CVE-2021-44228 — Log4Shell
The Log4j 2.x logger interpolated ${jndi:ldap://...} strings
when logging arbitrary content. An attacker who could get a
string into a log statement (almost any user-controlled
field — User-Agent, search query, username) could trigger an
LDAP/RMI lookup, fetch a remote class, and execute it. The
naive fix (upgrade to 2.15.0) had a follow-up CVE because the
fix was incomplete; the durable fix is 2.17.1+ plus class
removal.
Affected versions
- Log4j-core 2.0-beta9 through 2.14.1 — vulnerable.
- Log4j-core 2.15.0 — incomplete fix (CVE-2021-45046).
- Log4j-core 2.16.0 — DoS via uncontrolled recursion (CVE-2021-45105).
- Log4j-core 2.17.0 — JDBC Appender RCE under specific config (CVE-2021-44832).
- Log4j-core 2.17.1+ (or 2.12.4 / 2.3.2 for older Java versions) — the durable fix.
log4j-api alone is not vulnerable; the bug is in
log4j-core’s pattern-substitution code path.
Indicator-of-exposure
Having log4j-core in the classpath is the necessary
condition. Sufficient exposure also requires:
- A vulnerable version (per the above).
- A code path that logs untrusted input. In a typical web application, almost every code path qualifies — request headers, paths, bodies are routinely logged.
If the only logging in the application is a fixed string with no user-controlled fields and no exception traces (rare), exposure may be limited. Don’t rely on that for triage; the attacker’s job is to find one untrusted input that makes it to a log line.
Remediation strategy
The full fix is all of:
- Upgrade
log4j-coreto 2.17.1+ (or 2.12.4 / 2.3.2 on older Java versions). - Set the JVM flag
-Dlog4j2.formatMsgNoLookups=true(or theLOG4J_FORMAT_MSG_NO_LOOKUPS=trueenvironment variable) for defence-in-depth. - Remove
JndiLookup.classfrom any vendored Log4j JAR that cannot be upgraded. - Audit for
log4j-1.x. The 1.x branch has different CVEs (CVE-2019-17571, CVE-2022-23305) and is end-of-life.
Steps 2 and 3 are the mitigation path when the upgrade can’t ship immediately; both are still reasonable defence-in-depth after the upgrade.
When to use it
Use this recipe when a Java, Scala, Kotlin, Clojure, Spark, Hadoop, Solr,
Elasticsearch, vendor appliance, container image, or shaded/uber-JAR may include
log4j-core 2.x in the vulnerable ranges or Log4j 1.x legacy appenders. It is
most important when untrusted request fields, headers, exception messages,
tenant data, job metadata, or message payloads reach application logs.
Use it to inventory direct, transitive, shaded, vendored, and runtime-bundled Log4j copies, upgrade them on the correct Java line, apply defense-in-depth, and prove JNDI lookup behavior is neutralized. Do not use it to batch unrelated dependency upgrades into the same PR.
Inputs
- Maven, Gradle, SBT, Bazel, Pants, Ivy, lockfiles, dependency reports, SBOMs, container images, shaded JARs, vendor bundles, deployment manifests, JVM startup config, Helm values, Dockerfiles, and service launchers.
- Resolved
log4j-core,log4j-api, Log4j 1.x, reload4j, bridge, appender, and transitive dependency versions for every module and artifact. - Logging paths that can receive untrusted input: request headers, paths, bodies, usernames, search queries, exception messages, job names, queue payloads, tenant metadata, and audit events.
- Mitigation and verification evidence: JVM flags, environment variables,
JndiLookup.classremoval, SCA/SBOM scans, behavior tests, network egress policy, and runtime startup logs. - Java runtime constraints, vendored/signed artifact ownership, module owners, rollout windows, rollback plan, and scanner suppression requirements.
The prompt
You are remediating Log4Shell (CVE-2021-44228 / 45046 / 45105
/ 44832) in this repository. Output a PR (or set of PRs, one
per Maven module) or a TRIAGE.md.
## Step 0 — Inventory
1. Locate every `log4j-core` dependency in the dependency
graph: `pom.xml`, `build.gradle`, `build.sbt`, the lockfile
if any, and shaded/uber-JAR contents.
2. Record the current version of each. Note any vendored or
shaded copies.
3. List Log4j 1.x usages separately — they need a different
fix path (replace with reload4j or upgrade to 2.x).
## Step 1 — Apply the upgrade
For each `log4j-core` reference:
1. Bump the version to 2.17.1+ (or 2.12.4 on Java 7, 2.3.2 on
Java 6). Match the major-runtime constraint of the project.
2. Bump `log4j-api` to the same version. Mismatch breaks at
runtime.
3. Run the project's test suite. If tests fail because of
genuinely-changed behaviour (rare in 2.14 → 2.17), document
the change in the PR.
## Step 2 — Defence-in-depth
1. Add `-Dlog4j2.formatMsgNoLookups=true` to the JVM args
in the project's launcher / Dockerfile / deploy
configuration.
2. For any vendored / shaded JAR that cannot be upgraded
immediately, remove `JndiLookup.class` from the JAR:
`zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class`.
3. Verify with `unzip -l log4j-core-*.jar | grep JndiLookup`
showing no entry.
## Step 3 — Verify
1. Re-run the SBOM / SCA scan against the post-upgrade
artifact. CVE-2021-44228, -45046, -45105, and -44832 must
all show as "fixed" or "not present."
2. Run a behaviour test: a log statement that includes
`${jndi:ldap://example.invalid/x}` must log the literal
string, not trigger an LDAP request.
3. Confirm the JVM flag is being applied at runtime by
inspecting `java -XshowSettings:properties` output (or the
container's startup logs).
## Step 4 — Open the PR
- Branch: `remediate/cve-2021-44228-log4shell`.
- Title: `[Security][CVE-2021-44228] upgrade log4j-core to 2.17.1+`.
- Body must include:
- CVE summary and link to the advisory.
- Per-module list of versions bumped.
- JVM flag changes.
- Class-removal actions on any vendored JARs.
- SCA scan output before/after.
- Behaviour-test result.
- Rollback plan.
- Label: `sec-auto-remediation`.
## Stop conditions
- A vendored / shaded JAR cannot be safely modified
(signed, license-restricted). Triage with a note about the
vendor.
- The application uses Log4j 1.x and replacement requires API
changes. Triage; the right path is a separate
Log4j-1-to-2-or-reload4j migration.
- Tests fail in a way that suggests a real behaviour change in
Log4j 2.17.1+ — read the release notes and document.
## Scope
- Do not bundle other CVE bumps in this PR.
- Do not modify application logging configuration beyond what
the recipe requires.
- Do not remove `JndiManager` or other classes; only
`JndiLookup.class` is the fix.
Verification — what the reviewer looks for
- Both
log4j-coreandlog4j-apiare at the same patched version. - The JVM flag is applied at the deployment layer (Dockerfile, Helm values, systemd unit), not just in a test config.
- For vendored JARs: the
JndiLookup.classentry is gone. Confirm withunzip -l. - The behaviour test in the PR exercised the actual logger path the application uses (not a synthetic logger).
- The PR did not silently bump unrelated dependencies.
Watch for
- Shaded uber-JARs. Some applications shade Log4j into a bigger JAR. Bumping the dependency upstream isn’t enough; the shaded copy needs the patched version baked in.
- Java 6 / Java 7 targets. The patch backports for those runtimes are 2.3.2 and 2.12.4. Do not silently bump to 2.17.1 on a Java 7 build.
log4j-1.x. A different bug surface (CVE-2019-17571 deserialization, CVE-2022-23305 SQL appender). The fix is migration to 2.x or reload4j; the recipe above does not cover it.- JNDI is broader than Log4j. A Log4j-clean codebase can still have JNDI-injection bugs in other libraries (Spring, H2 Console, JNDI lookups in custom code). This recipe doesn’t catch those.
- The flag is not enough alone.
formatMsgNoLookups=truewas a partial mitigation pre-2.16; some pattern-layout configurations still resolved lookups. The upgrade is the durable fix.
Output contract
Return one of:
- A reviewer-ready PR/change request that inventories every Log4j copy, upgrades
log4j-coreandlog4j-apito the correct patched line, removesJndiLookup.classfrom any unavoidable vendored JAR, applies deployment-layer defense-in-depth, adds a behavior test, refreshes SCA/SBOM evidence, and documents rollback. TRIAGE.mdwhen a vendored/signed artifact cannot be safely modified, Log4j 1.x migration requires a broader API change, Java runtime constraints block the patched line, or ownership is outside the repository.
The output must list each module/artifact, before/after versions, Java runtime
constraint, shaded or vendored JAR handling, JVM flag location, behavior-test
result, SCA/SBOM scan status, rollback plan, and any Log4j 1.x follow-up. It
must not rely on formatMsgNoLookups alone, silently bump unrelated
dependencies, remove unrelated JNDI classes, or suppress scanner findings
without evidence.
References
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2021-44228
- GitHub Advisory: https://github.com/advisories/GHSA-jfh8-c2jp-5v3q
- Apache Log4j security page: https://logging.apache.org/log4j/2.x/security.html
Related recipes
- Vulnerable Dependency Remediation — the generic workflow this recipe specialises.
- Classic Vulnerable Defaults → Java ObjectInputStream — adjacent Java deserialization risks.