Safeguard
Vulnerability Management

CVE-2021-45105: the Log4j denial-of-service flaw recursion built

CVE-2021-45105 scored CVSS 5.9 and let a single crafted lookup string crash a JVM with a StackOverflowError — no RCE required, just uncontrolled recursion.

Safeguard Research Team
Research
Updated 6 min read

On December 18, 2021, six days after Apache patched the Log4Shell remote-code-execution flaw and one day after a second patch (CVE-2021-45046) proved insufficient, NVD published CVE-2021-45105 — a third Log4j2 vulnerability in the same disclosure wave. Unlike its predecessors, this one carried no path to remote code execution. Its CVSS 3.1 base score was 5.9 (medium), driven by a vector of AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H — network-reachable, no privileges or user interaction needed, zero confidentiality or integrity impact, but high availability impact. The mechanism was uncontrolled recursion: Log4j2 versions 2.0-alpha1 through 2.16.0, excluding backported fixes 2.12.3 and 2.3.1, "did not protect from uncontrolled recursion from self-referential lookups," per the official NVD description. An attacker who could influence Thread Context Map (MDC) data could craft a lookup string that referenced itself, driving the lookup-evaluation engine into infinite recursion until the JVM threw a StackOverflowError and the application died. It's a smaller, quieter bug than Log4Shell — but it's a textbook case of how input validation gaps and recursion without bounds combine into denial of service, and it's still worth understanding for anyone triaging the long tail of Log4j findings in 2026.

What actually causes the recursion in CVE-2021-45105?

The root cause is that Log4j2's lookup-evaluation logic — the code that resolves ${...} style substitutions in log patterns, including Thread Context Map values — had no depth limit or cycle detection when a lookup key resolved back into another lookup that referenced itself. NVD classifies this under two weakness types: CWE-20 (Improper Input Validation) and CWE-674 (Uncontrolled Recursion). Log4Shell (CVE-2021-44228) was also a lookup-evaluation bug, but it abused JNDI lookups to fetch and execute remote code; this flaw doesn't need JNDI at all. If an application logs attacker-influenced context data — a common pattern in web frameworks that stuff request headers or user IDs into MDC for structured logging — and that data contains a self-referential lookup expression, evaluating it recurses without ever terminating. The stack fills up and the thread crashes with a StackOverflowError, which in many deployments takes down the whole service or worker process.

Which versions are affected and which are safe?

The affected range is broad: Log4j2 2.0-alpha1 through 2.3.0, 2.4 through 2.12.2, and 2.13.0 through 2.16.0 — effectively every 2.x release available at the time, since 2.16.0 was itself the emergency patch for the prior two CVEs and still hadn't closed this recursion path. The fix shipped in Log4j 2.17.0, released the same day as the CVE's publication, along with two backports for teams unable to jump to 2.17.0 directly: 2.12.3 for the Java 7 line and 2.3.1 for the Java 6 line. If your dependency tree resolves to any version outside 2.17.0, 2.12.3, or 2.3.1, you are exposed. Note that this is specifically about the Log4j2 line — if you're tracking down a Log4j 1.2 vulnerability instead, you're looking at a different, much older codebase (last released in 2015, with its own separate advisories like CVE-2019-17571) that isn't covered by this Log4j vulnerability version fix at all and needs its own remediation path, typically a migration off 1.x entirely. Given how often Log4j sits several levels deep as a transitive dependency of logging frameworks, application servers, or third-party libraries rather than a direct import, version-string matching against a manifest alone is not a reliable way to confirm you're clear — you need to know what your build actually resolves, not what a top-level POM declares.

Why does this matter less than Log4Shell but still matter?

It matters less because there's no data exfiltration or code-execution path — CVSS confidentiality and integrity impacts are both "none." It still matters because availability is a real security property, and CWE-674 uncontrolled-recursion bugs are cheap to trigger and hard to rate-limit around once an attacker has any foothold that reaches logged data. The attack complexity is rated "high" in the CVSS vector precisely because the attacker needs a way to get a crafted string into Thread Context Map data that eventually gets logged — not trivial, but achievable in applications that log request headers, cookies, or other user-controllable fields for observability. For an on-call team, the practical impact is identical to any other crash-loop DoS: repeated StackOverflowErrors can degrade or take down a service, and if the affected process is a shared logging pipeline or sidecar, the blast radius extends to everything that depends on it.

How should teams triage this alongside the other Log4j CVEs?

Because CVE-2021-45105 shares an ecosystem and a disclosure window with CVE-2021-44228 and CVE-2021-45046, it's easy for triage tooling to collapse it into "the Log4j thing we already patched" — which is a mistake if the actual fix applied was only the 2.15.0 or 2.16.0 patch for the RCE issues rather than the full 2.17.0 upgrade. A vulnerability management program should treat each CVE against its own fixed-version boundary rather than assuming one Log4j upgrade closes every finding in the family. Because Log4j is so frequently pulled in transitively — through a logging abstraction, an application framework, or a vendored dependency — software composition analysis needs to resolve the actual dependency graph, not just direct declarations, to confirm which patched version, if any, is actually on the classpath at runtime.

How does Safeguard help with findings like this?

Safeguard's deep dependency scanning walks resolved Java dependency graphs to depth 100 — well past where most SCA tools stop — specifically because packages like Log4j routinely surface many levels below a project's direct pom.xml or build.gradle declarations rather than as a top-level dependency. For each resolved Log4j2 node, Safeguard checks the exact version against the known-vulnerable ranges for CVE-2021-45105 (and the related CVE-2021-44228 and CVE-2021-45046 findings), confirms whether the fix lands in 2.17.0, 2.12.3, or 2.3.1, and captures the full transitive path from your application down to the vulnerable node so your team can see exactly which import chain pulled it in. Findings carry their CVSS score and CWE mapping — CWE-20 and CWE-674 here — into the same policy-gate workflow used for every other finding, so a team can set a rule that treats an unpatched Log4j recursion bug as a release blocker without hand-triaging version strings across every service in the portfolio.

Never miss an update

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