KGCS Documentation

02 — Root Cause Analysis: CWE-91

CWE-91: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')

While CWE-91 is generically about "Special Elements," in the context of Log4j it refers to the improper handling of JNDI lookup syntax in log messages.

Technical Breakdown

How Log4j Processes Messages

Log4j has a feature called "Lookups" that allows dynamic value insertion in log messages:

// Standard logging
logger.info("User: " + username);  // Output: User: alice

// With Log4j Lookups
logger.info("User: ${java:version}");  // Output: User: 11.0.13

// JNDI Lookup (the vulnerable feature)
logger.info("Session: ${jndi:ldap://ldap.example.com/ou=Sessions}");

The problem: Log4j trusts the input without validating what comes after jndi:.

The Vulnerability Chain

User Input (untrusted)
  ↓
Log4j receives input in log message
  ↓
Log4j parses $ {...} pattern
  ↓
Creates JNDI Context
  ↓
Attempts to load remote resource (LDAP, RMI, DNS)
  ↓
Remote server responds with malicious serialized object
  ↓
Java deserializes object
  ↓
Code in object constructor executes
  ↓
✓ Remote Code Execution (RCE)

Why This is CWE-91

CWE-91 definition: "Improper Neutralization of Special Elements used in an SQL Command"

In Log4j context (adapted): Improper Neutralization of Special Elements used in a Lookup Expression

Code-Level Analysis

Vulnerable Code (Simplified - Log4j 2.14.x):

public class StrSubstitutor {
    public String replace(String source) {
        if (source == null) {
            return null;
        }
        // Parse for ${...} patterns
        // If pattern is ${jndi:*}, create JNDI lookup
        // Execute the lookup WITHOUT validation
        return resolveVariable(source);  // ← VULNERABILITY HERE
    }

    private String resolveVariable(String value) {
        if (value.startsWith("jndi:")) {
            // No validation - just execute!
            return executeJndi(value);  // ← UNVALIDATED EXECUTION
        }
        return value;
    }
}

Patched Code (Log4j 2.17.0+):

public class StrSubstitutor {
    private static final boolean IGNORE_LOOKUPS =
        !Boolean.parseBoolean(System.getProperty("log4j2.enableJndiLookup", "false"));

    public String replace(String source) {
        if (source == null) {
            return null;
        }
        // JNDI lookups disabled by default in patched version
        if (IGNORE_LOOKUPS && source.contains("jndi:")) {
            return source;  // Don't process JNDI lookups
        }
        return resolveVariable(source);
    }
}

The fix: Disable JNDI lookups by default and require explicit opt-in.

CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')

CWE-502: Deserialization of Untrusted Data

CWE-94: Improper Control of Generation of Code ('Code Injection')

Attack Surface in Log4j

Any part of your application that logs user-controlled data is attack surface:

Source Example KGCS Category
HTTP query parameters ?search=${jndi:ldap://...} User Input
HTTP headers User-Agent: ${jndi:ldap://...} User Input
Form fields Username field contains payload User Input
API JSON payloads {"email": "${jndi:ldap://...}"} User Input
Reverse DNS logs Server logs reverse DNS, which contains payload Context-Dependent
Email headers Email subject/sender Context-Dependent
Browser user agent Logged for analytics Consumer Input

Exploitation Prerequisites

An attacker needs:

  1. ✓ A way to inject at least one log message containing ${jndi:...}
  2. ✓ That message must be processed by vulnerable Log4j
  3. ✓ Access to external resource server (LDAP, RMI, or DNS)
  4. ✓ Network connectivity from target to attacker's infrastructure

All four conditions are almost always met in real deployments.

Why This Weakness is Dangerous

Design Issue

The original Lookup feature was designed with assumption: "Log messages are internal, trusted, generated by developers."

This assumption was violated in modern applications where:

Defense-in-Depth Failure

Standard defenses don't prevent this:

Mitigation at the Code Level

For Application Developers

// BAD: Logs user input directly
String userInput = request.getParameter("q");
logger.info("Search: " + userInput);  // VULNERABLE if userInput contains ${jndi:...}

// BETTER: Sanitize/escape user input
String userInput = request.getParameter("q");
String sanitized = userInput.replace("$", "");  // Remove $ chars
logger.info("Search: " + sanitized);  // Safer but not foolproof

// BEST: Upgrade Log4j and disable lookups
// (System admin responsibility, not application code)

For Log4j Developers

The fix in 2.17.0:

  1. Disable JNDI lookups by default (breaking change, but necessary)
  2. Require log4j2.enableJndiLookup=true JVM flag to enable them
  3. Document the security implications

KGCS Mapping

In the KGCS graph:

CVE-2021-44228  (specific instance of the weakness)
  ↓ CAUSED_BY
CWE-91  (general weakness class)
  ↓ DEMONSTRATED_BY (how to exploit it)
[CAPEC-88: OS Command Injection]
[CAPEC-242: Code Injection via Lookup Expression]

All three are connected in KGCS, showing that:

Next Steps

Read: 03 Attack Scenarios — See how attackers weaponize this weakness

Or Jump To: 06 Reasoning Walkthrough — See KGCS analysis in action


Sources: CWE-91 Definition, CVE-2021-44228 Advisory, Log4j Security Docs, Apache Security Guide