Vulnerabilities
7 min readUpdatedLog4Shell (CVE-2021-44228): the Log4j JNDI lookup flaw
Quick answer
Log4Shell, CVE-2021-44228, is a remote code execution flaw in Apache Log4j 2. Because Log4j expanded lookup expressions inside log messages, a string an attacker could get logged — in a header or form field — could make the server fetch and run code over JNDI. It scored CVSS 10.0, was disclosed on 9 December 2021, and is in CISA's KEV catalogue. It affects Log4j 2.0-beta9 through 2.14.1, except the 2.12.2 and 2.3.1 back-ports; upgrade to 2.17.1, 2.12.4 or 2.3.2.
At a glance
critical severity- Identifier
- CVE-2021-44228
- Also known as
- Log4Shell
- Product
- Apache Log4j 2 (log4j-core)
- Affected
- 2.0-beta9 through 2.14.1 (i.e. < 2.15.0, excluding back-ports 2.3.1 and 2.12.2)
- Fixed in
- This CVE: 2.15.0 (Java 8+), 2.12.2 (Java 7), 2.3.1 (Java 6); All four Log4Shell CVEs: 2.17.1 (Java 8+), 2.12.4 (Java 7), 2.3.2 (Java 6)
- CVSS
- 10.0 (CVSS 3.1, NVD)
- Disclosed
- Known exploited (CISA KEV)
- Yes
In CISA KEV. Mass-exploited since December 2021 and still listed among routinely exploited vulnerabilities years later.
01What is Log4Shell?
Log4Shell is a remote code execution flaw in Apache Log4j 2, one of the most widely used Java logging libraries. The root cause is a feature: Log4j expanded special ${...} lookup expressions inside the text it logged. One of those lookups, JNDI, could be pointed at an attacker-controlled directory server, and the object it returned could cause the application to load and run remote code.
What made it catastrophic is how little the attacker needed. Any input that eventually gets logged — a User-Agent header, a form field, a chat message, a username — could carry the expression. The attacker did not need an account, a session, or any access; they only needed the application to log a string they controlled. That combination of trivial delivery and full code execution is why it scored a perfect CVSS 10.0.
02Affected and fixed versions, including the follow-ups
The original fix was incomplete, and three more CVEs followed within three weeks as researchers probed the patches. This is normal for a high-profile flaw — the React Server Components and Redis Lua advisories later followed the same pattern — so "Log4Shell is patched" was never the same as "this version is safe." The clean target is 2.17.1 (or 2.12.4 / 2.3.2 for older Java).
| CVE | Type | CVSS 3.1 | Fixed in (Java 8 / 7 / 6) |
|---|---|---|---|
| CVE-2021-44228 | JNDI lookup RCE | 10.0 | 2.15.0 / 2.12.2 / 2.3.1 |
| CVE-2021-45046 | Thread Context lookup RCE | 9.0 | 2.16.0 / 2.12.2 (Apache now lists 2.12.3) / 2.3.1 |
| CVE-2021-45105 | Recursive-lookup denial of service | 5.9 | 2.17.0 / 2.12.3 / 2.3.1 |
| CVE-2021-44832 | JDBC appender RCE (config write) | 6.6 | 2.17.1 / 2.12.4 / 2.3.2 |
03How an attack worked, conceptually
The attacker placed a JNDI lookup expression in any value that would be logged. When Log4j rendered the log line, it resolved the lookup, reached out to the attacker's directory server, and — on affected Java runtimes — fetched and instantiated a remote object, which ran the attacker's code. On its own, a lookup could also cause the server to make an outbound DNS or LDAP request, which attackers used as a quick, low-noise way to confirm a target was vulnerable.
Even where the direct code-load path was blocked by a modern Java runtime, the lookups could still read local data such as environment variables and smuggle it out through the hostname of the outbound request. That is why upgrading the JDK was never a complete fix — the only real remedy was to remove the dangerous behaviour from Log4j itself.
04How to find vulnerable Log4j
The hard part of Log4Shell was never the patch; it was finding every copy. Log4j is often bundled deep inside other jars, wars and ears — shaded, repackaged, three dependencies down — so a simple package query misses most of it. Search the filesystem for the JndiLookup class and for log4j-core jars, looking inside archives, not just at their names.
# Find archives that contain the vulnerable class or a log4j-core jar
find / -xdev -type f \( -name '*.jar' -o -name '*.war' -o -name '*.ear' \) 2>/dev/null |
while IFS= read -r f; do
python3 -m zipfile -l "$f" 2>/dev/null |
grep -qE 'org/apache/logging/log4j/core/lookup/JndiLookup\.class|log4j-core-[0-9][^/ ]*\.jar' &&
echo "$f"
done
# Read the exact version from a jar's embedded metadata
python3 -c 'import sys,zipfile; print(zipfile.ZipFile(sys.argv[1]).read(
"META-INF/maven/org.apache.logging.log4j/log4j-core/pom.properties").decode())' log4j-core-*.jar05How to fix and mitigate it
Upgrade log4j-core to 2.17.1 (Java 8+), 2.12.4 (Java 7) or 2.3.2 (Java 6). If you cannot upgrade immediately, the supported stopgap is to physically remove the JndiLookup class from the jar — this is the mitigation Apache endorsed, unlike the formatMsgNoLookups flag, which Apache later marked insufficient because other code paths could still reach the lookups.
- Upgrade log4j-core to a fully fixed release — 2.17.1, 2.12.4 or 2.3.2 — rather than stopping at 2.15.0
- If you cannot upgrade, delete the JndiLookup class from every copy and restart the app
- Do not rely on the formatMsgNoLookups flag alone; Apache classed it as insufficient
- Upgrading the JDK blocks the remote class-load path but not data exfiltration via lookups, so it is not a substitute for patching Log4j
# Only when you cannot upgrade yet; prefer upgrading to 2.17.1 / 2.12.4 / 2.3.2.
zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
# Then restart the application so the modified jar is reloaded.06Why WAF rules were a stopgap, not a cure
In the first hours, a web application firewall rule that blocked the obvious JNDI string genuinely bought time, and buying time mattered when the alternative was nothing. But attackers quickly obfuscated the expression using Log4j's own lookup grammar — lowercasing fragments, nesting lookups, splitting keywords — so that a blocked literal could be reconstructed by the logger after it passed the filter. Cloudflare reported attackers moving from plain ${jndi: strings to evasive forms within days. Broader rules, such as flagging any ${ in input, catch more variants but also legitimate input.
Be honest about what this means. A reverse proxy can reduce exploitation attempts, slow mass scanning and give you breathing room, and a proxy such as GuardX can absorb the flood of opportunistic probes that follows a disclosure. It cannot reliably stop a determined attacker against an unpatched server, because the vulnerable behaviour is inside your application and the input space is enormous. Edge filtering is a seatbelt, not an airbag: use it while you patch, not instead of patching. Virtual patching explains why syntax the application expands later is the hardest case for any rule.
| Control | What it does | Honest limit |
|---|---|---|
| WAF signature | Blocks obvious ${jndi:...} strings | Bypassed by lookup-grammar obfuscation |
| Block all ${ in input | Catches obfuscation attempts | High false-positive rate on legitimate input |
| Egress filtering (block outbound LDAP/RMI) | Stops the second-stage fetch | Does not stop DNS-based data exfiltration |
| Upgrade / remove JndiLookup | Removes the behaviour | The only complete fix |
07A long tail of exploitation
The Cyber Safety Review Board concluded that vulnerable instances of Log4j "will remain in systems for many years to come, perhaps a decade or longer," and CVE-2021-44228 was still on CISA's list of the top routinely exploited vulnerabilities of 2023. CVE-2021-45046 was added to the KEV catalogue in May 2023. The lesson for operators is that disclosure is the start, not the end: keep an inventory of what you run, keep protection always on rather than switched on during incidents, and expect scanning for old flaws to continue indefinitely.
| Date | Event |
|---|---|
| 24 Nov 2021 | Reported to Apache by Chen Zhaojun of Alibaba Cloud Security |
| 1 Dec 2021 | Earliest exploitation attempts Cloudflare found, eight days before disclosure |
| 9–10 Dec 2021 | Public disclosure; 2.15.0 published; added to CISA KEV on 10 Dec |
| 13–29 Dec 2021 | 2.16.0, 2.17.0 and 2.17.1, plus 2.12.x and 2.3.x back-ports, released as follow-up CVEs land |
| 11 Jul 2022 | US Cyber Safety Review Board calls Log4j an "endemic vulnerability" |
Questions
Frequently asked questions
Sources
Sources and verification
The jar-discovery and metadata commands were run against synthetic log4j-core jars and a war with GNU find 4.9.0 and Python 3.11 on Debian 12, October 2026. The zip -d stopgap is quoted verbatim from Apache's advisory and was not executed (zip is not installed on the test host). No exploit was run; payload details are intentionally omitted.
- 01NVD — CVE-2021-44228 · NVD
- 02Apache Log4j Security Vulnerabilities · Apache Logging Services
- 03Apache Log4j Security Vulnerabilities (January 2022 snapshot with the JndiLookup and formatMsgNoLookups guidance) · Apache Logging Services via Internet Archive
- 04CISA KEV catalogue · CISA
- 05Apache Log4j Vulnerability Guidance · CISA
- 06CVE-2021-44228 payloads captured in the wild · Cloudflare
- 07Exploitation of Log4j CVE-2021-44228 before public disclosure and evolution of evasion and exfiltration · Cloudflare
- 082023 Top Routinely Exploited Vulnerabilities (AA24-317A) · CISA
- 09CSRB Review of the December 2021 Log4j Event · Cyber Safety Review Board
Last updated