JNDI injection

Resource injection through an untrusted JNDI lookup name

Description

JNDI resolves objects and contexts by name. If untrusted input supplies the name for lookup, lookupLink or InitialContext.doLookup, an attacker can select the resource identifier that the application resolves.

InitialContext treats a string shaped like scheme_id:... as a URL name and looks for that scheme's URL context factory. LDAP, RMI, DNS or a custom provider may therefore cause unintended network connections, resource access, information disclosure or delays. Reconstructing a returned reference as an object may also lead to code execution, depending on the JDK, provider, object factories, classpath and serialization settings.

The protections in newer JDKs have specific limits. JDK 24 permanently disabled remote factory-class downloading in the built-in LDAP/RMI providers and removed the old trustURLCodebase properties. JDK 25's built-in LDAP provider does not reconstruct serialized data and related references unless com.sun.jndi.ldap.object.trustSerialData is explicitly true. These protections do not eliminate URL-triggered network access or risks from local factories, custom providers and a custom ObjectFactoryBuilder.

Potential impact

  • Server-side connections to attacker-selected LDAP, RMI, DNS or custom providers
  • Access to internal naming services or disclosure of resources behind a firewall
  • Thread and connection exhaustion caused by slow or unresponsive providers
  • Object-creation side effects or code execution with unsafe legacy, serialization or factory configurations

Remediation

  1. Keep complete JNDI names under developer control. Accept a business key and map it through a closed switch, enum or equivalent mapping to a fixed local name. Never append an untrusted suffix.
  2. If callers must supply names, compare them exactly against a finite set of reviewed complete local names. Prefix checks, regexes or scheme denylists such as ldap: and rmi: do not establish every possible provider or federated destination.
  3. Fix Context.INITIAL_CONTEXT_FACTORY, Context.PROVIDER_URL, Context.OBJECT_FACTORIES, Context.URL_PKG_PREFIXES and provider settings in trusted deployment configuration. Keep only trusted providers and factories on the classpath and review custom ObjectFactoryBuilder implementations separately.
  4. Use a supported, updated JDK 25. Removed trustURLCodebase properties are not a remedy. Keep LDAP trustSerialData disabled and restrict jdk.jndi.object.factoriesFilter and provider-specific filters to necessary factories.
  5. Restrict outbound access to required naming servers and use least-privilege credentials. For the built-in LDAP provider, configure com.sun.jndi.ldap.connect.timeout and com.sun.jndi.ldap.read.timeout. These reduce impact but do not make attacker-controlled names safe.

Examples

Before

java
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;

class UnsafeJndiLookup {
    Object lookup(HttpServletRequest request) throws NamingException {
        String name = request.getParameter("name");
        Context context = new InitialContext();
        return context.lookup(name); // BAD: request input can select a provider and resource
    }
}

After

Map the external key to a complete application-owned local name. The switch result contains no request text.

java
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;

class SafeJndiLookup {
    Object lookup(HttpServletRequest request) throws NamingException {
        String key = request.getParameter("name");
        if (key == null) {
            throw new NamingException("resource not selected");
        }

        String jndiName = switch (key) {
            case "mainDs" -> "java:comp/env/jdbc/MainDS";
            case "ordersQ" -> "java:comp/env/jms/OrdersQueue";
            default -> throw new NamingException("resource not allowed");
        };

        return new InitialContext().lookup(jndiName);
    }
}

References