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:

  1. Upgrade log4j-core to 2.17.1+ (or 2.12.4 / 2.3.2 on older Java versions).
  2. Set the JVM flag -Dlog4j2.formatMsgNoLookups=true (or the LOG4J_FORMAT_MSG_NO_LOOKUPS=true environment variable) for defence-in-depth.
  3. Remove JndiLookup.class from any vendored Log4j JAR that cannot be upgraded.
  4. 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.class removal, 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-core and log4j-api are 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.class entry is gone. Confirm with unzip -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=true was 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-core and log4j-api to the correct patched line, removes JndiLookup.class from any unavoidable vendored JAR, applies deployment-layer defense-in-depth, adds a behavior test, refreshes SCA/SBOM evidence, and documents rollback.
  • TRIAGE.md when 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