Spring4Shell: The RCE That Wasn’t Log4Shell (but Still Bit Hard)

📋 Key Takeaways
  • What happened
  • The exploit chain
  • Impact and numbers
  • Timeline
  • Why it still matters in 2026
9 min read · 1,610 words
Educational & Ethical Use Only — This article is provided for educational and ethical cybersecurity research purposes only. The techniques described should only be used on systems you own or have explicit permission to test. Always follow responsible disclosure and the laws applicable to you. Mitigations are included so engineers can harden real systems.

On 31 March 2022, the Spring team shipped emergency patches for Spring Framework, closing a remote code execution flaw that the internet had spent 48 hours panicking about under the inevitable nickname Spring4Shell. The vulnerability — CVE-2022-22965, affecting Spring MVC and WebFlux applications running on JDK 9 or later that bind non-primitive request parameters to POJOs — let an attacker abuse Spring’s data binder to reach an object’s ClassLoader through the crafted class.module.classLoader.* property chain, and on the most common deployment shape (WAR on Tomcat) rewrite the AccessLogValve configuration to write a JSP webshell into a web-served directory: file-write to code execution in one request. The panic was understandable — Spring is the backbone of uncountable enterprise Java stacks — but the payload reality was narrower than the headlines: the bug required JDK 9+, a form-binding code pattern many apps didn’t use, and the known turnkey exploit targeted Tomcat specifically, which is why the post-mortem consensus ranked it serious-but-not-Log4Shell. Patches (5.3.18 / 5.2.20) plus rapid guidance from Spring, VMware, and cloud vendors blunted the initial wave; mass scanning began almost immediately, opportunistic attacks followed through April, and CISA added the CVE to its Known Exploited Vulnerabilities catalog — but the widespread enterprise-pocalypse never materialised, and Spring4Shell settled into its real historical role: a masterclass in how a popular framework’s disclosure communications can amplify or dampen a vulnerability’s real risk.

Quick Answer
CVE-2022-22965 (“Spring4Shell”, patched 2022-03-31): RCE in Spring Framework’s data binding. Affects Spring MVC/WebFlux apps that bind non-primitive POJO request parameters (form-binding), running on JDK 9+. Root cause: Spring’s BeanWrapperImpl property resolution was bypassed by Java 9’s module system changes — Class.getModule() now reachable via property path — enabling class.module.classLoader.* chains that earlier JDKs’ Tomcat hardening (the CVE-2010-1622 lineage) had deliberately blocked. Best-known gadget (Apache Tomcat): attacker sets AccessLogValve properties (pattern/suffix/prefix/directory) via bound parameters → Tomcat writes attacker-controlled “log” as a .jsp webshell to a webroot-served path → full RCE. Patched in 5.3.18/5.2.20 (disallow Class getClassLoader/module binding). Preconditions kept blast radius < Log4Shell: JDK 9+, Spring MVC/WebFlux, WAR on servlet container (known exploit chain Tomcat-specific), and a controller binding non-primitive types. Mass scanning started ~2022-04; CISA KEV-listed; real-world victims mostly unpatched internet-facing Tomcat apps (some reports of ransomware/coinminer drop later). Separate but adjacent: CVE-2022-22963 (Spring Cloud Function routing SpEL RCE, also patched late March) — many early reports conflated the two.

What happened

The sequence was speed-run. On 29 March 2022, rumours of a Spring Framework 0-day RCE spread from Chinese-language security chatter after a researcher demonstrated the classloader-binding chain (the report reportedly reached Spring via coordinated disclosure); within a day a PoC was circulating publicly. On 31 March, Spring released 5.3.18 and 5.2.20 with the fix (property binding on Class.getClassLoader() and Class.getModule() disallowed) plus a detailed explainer of the exact preconditions — a document that did more than the patch to calm the panic, because it let operators self-triage. In parallel, a genuinely different bug in Spring Cloud Function (CVE-2022-22963, an unauthenticated SpEL injection in the routing expression) was also going public, and early coverage smeared the two together, inflating the perceived blast radius.

Exploitation followed the usual curve: internet-wide scanning for the Tomcat signature within days, exploit kits and botnet loaders (including Mirai variants and coinminers) by mid-April, and CISA’s KEV listing on 4 April formalising federal patch deadlines. But apps that actually matched the precondition set — JDK 9+, form-binding of non-primitive parameters, WAR-on-Tomcat exposure — were a distinct minority of the Spring ecosystem, and most enterprises had patched inside the two-week window. Damage reports existed (compromised e-commerce and internal apps, webshell persistence) but stayed anecdotal rather than systemic.

The exploit chain

Spring4Shell request path (Tomcat gadget):

  attacker HTTP request:
    POST /path/formAction
    class.module.classLoader.
      resources.context.parent.pipeline.
        first.pattern=%{c2}i ...
        first.suffix=.jsp
        first.directory=webapps/ROOT
        first.prefix=shell

  WHY IT WORKS
    JDK 9+ exposes Class.getModule()
    as bean property "module"
    -> binder escapes the POJO's own
       fields into the ClassLoader
    -> Spring's disallowed-fields
       list only covered JDK 6-8
       paths (post-CVE-2010-1622
       Tomcat hardening)

  EFFECT (Tomcat-specific gadget)
    AccessLogValve reconfigured:
      prefix/suffix/directory/pattern
    next logged request rendered
    into webapps/ROOT/shell.jsp
    request-header payload (via
    %{c2}i / %{c1}i patterns) becomes
    executable JSP content
    -> webshell -> RCE

  PRECONDITIONS (all required for
  the known chain)
    JDK 9+
    Spring MVC or WebFlux
    parameter binding to POJO
    (non-primitive, non-String)
    WAR on servlet container
    (turnkey exploit = Tomcat)

  PATCH (2022-03-31)
    Spring 5.3.18 / 5.2.20
    + earlier mitigations: global
      WebDataBinder disallowedFields
      (class.module.classLoader etc.)
data-hmmnm-seam="2">

Impact and numbers

Metric Value
CVE / patch date CVE-2022-22965 / 2022-03-31 (5.3.18, 5.2.20)
Affected versions Spring Framework 5.3.0–5.3.17, 5.2.0–5.2.19 (older unsupported also affected)
Root cause class Data binding to ClassLoader via JDK 9+ module property path
Turnkey exploit target Tomcat (AccessLogValve → JSP webshell write)
Public PoC circulated 2022-03-30
CISA KEV listing 2022-04-04
Exploitation observed Mass scanning from early April; webshells, coinminers, Mirai-variant loaders
Commonly confused sibling CVE-2022-22963 (Spring Cloud Function SpEL RCE — separate bug, patched 2022-03-29 era)
data-hmmnm-seam="3">

Timeline

Date Event
2022-03-29 Rumours of Spring 0-day spread from Chinese security community chatter; researcher (via Code White) had reported the chain responsibly
2022-03-30 PoC circulating publicly; vendors rush guidance amid Log4Shell-flashback coverage
2022-03-31 Spring ships 5.3.18/5.2.20 + preconditions explainer; “Spring4Shell” name sticks
2022-04-01→04-04 Internet-wide Tomcat-signature scanning detected; CISA adds to KEV with federal patch deadline
2022-04 mid Opportunistic exploitation (webshells, cryptominers, botnet loaders) against unpatched internet-facing apps
2022 later Consensus assessment: real but bounded damage; lesson absorbed on framework-disclosure communications and JDK-version EOF hygiene
data-hmmnm-seam="4">

Why it still matters in 2026

Three reasons it outlived its news cycle. First, the mechanism: Spring4Shell was a regression of a lesson already learned — the classloader-binding family dates to CVE-2010-1622 (Springbean wrapper → Tomcat classloader), and Spring’s blocklist silently rotted when JDK 9’s module system reopened a path around it. That is the canonical argument for treating framework-level input-filtering blocklists as version-coupled attack surface: every runtime underneath (JDK, container, app server) change can resurrect a buried bug, and “we blocked that class of thing in 2014” is not a durable control. Second, the disclosure dynamics: the 29–31 March window featured leaking rumour, a public PoC ahead of coordinated advisory, a name engineered for Log4Shell resonance, and a sibling CVE being conflated in — every ingredient of the modern zero-day infohazard. Spring’s fast, precondition-explicit response became the counter-template vendors now imitate: publish the exact decision tree operators can self-triage against, and name-flames die of starvation. Third, the graveyard of unpatched long-tail: like every KEV entry, Spring4Shell never truly ended — scans for the endpoint signature continue years later, flipping forgotten Tomcat boxes in enterprise DMZs, which is why legacy Java estate and JDK-version awareness remain standing line-items in 2026 audit checklists. And in the bigger 2022 narrative, it was chapter two of “the supply chain’s runtime is the attack surface” — Log4Shell in December, Spring4Shell in March, Confluence’s OGNL in June: the year the industry learned that a single popular library’s parsing code is everyone’s perimeter.

data-hmmnm-seam="5">

Detection and hardening takeaways

  • Patch the framework, not just the app. Spring Framework 5.3.18/5.2.20 was the fix; application-level mitigations (global disallowedFields for class.module.classLoader) were stopgaps for estates that couldn’t rebuild immediately — audit that no stopgap became permanent.
  • Treat parameter-binding surfaces as parsing surfaces. Any controller binding request data to rich object graphs is one upstream bug away from type-confusion-style escapes; prefer DTOs with primitives/Strings, strict allowlists, and JDK/container awareness in threat models.
  • Harden the gadget, blunt the exploit. The turnkey chain needed Tomcat’s AccessLogValve default reachability and a writable webroot; running apps as non-root with read-only webroots, minimal valve surface, and filesystem integrity monitoring converts “webshell write” into “failed write and alert”.
  • Assets + versions = response speed. Operators who could instantly query “which of our apps are JDK 9+, Spring MVC, WAR on Tomcat, internet-facing?” triaged in minutes; SBOMs and runtime inventory are what turned Spring panic into a spreadsheet column.
  • Watch for the exploit’s signature, not just the CVE number. Requests containing class.module.classLoader, pipeline.first, and unusual AccessLogValve pattern tokens are detectable at WAF/ingress years after patching — the long-tail scanners never stopped, and neither should the detection rule.

FAQ

Was Spring4Shell as bad as Log4Shell?

No — narrower on every axis. Log4Shell was a logging-library bug that triggered on essentially any logged string, anywhere, default-on; Spring4Shell required JDK 9+, Spring MVC/WebFlux, a controller binding non-primitive POJO parameters, and (for the turnkey public exploit) a WAR deployment on Tomcat. The affected shape existed in the wild — enough for real compromises — but most Spring estates had patched or didn’t match the pattern, and the doom-scenario worm never came. The shared DNA: both were wildly popular libraries, both were discovered via demo PoCs outpacing advisories, and both lived in the patch long-tail for years afterwards.

What did the exploit actually do on a vulnerable server?

On Tomcat, reconfigured the access-log valve (filename, directory, and log-line pattern) so the next logged request’s attacker-controlled headers got written into a .jsp file inside a served directory — a webshell. From there: arbitrary command execution under the app’s privileges, credential harvesting, pivoting into the container host in bad configurations. Many observed in-the-wild payloads were mundane (cryptominers, Mirai-family botnet loaders), with ransomware and data theft showing up opportunistically rather than as a coordinated campaign.

Is the classloader-binding bug class dead now?

The specific CVE is fully patched, but the class is immortal: any bean-property/OGNL/SpEL-style expression engine that can walk from user input to ClassClassLoader → container internals is a rediscovery waiting for the next JDK or framework change — exactly what happened between 2010’s CVE-2010-1622 and 2022’s Spring4Shell when JDK 9 modules reopened the door Spring had bolted shut. Defensive posture: keep frameworks current, keep expression-driven binding out of internet-facing endpoints, and maintain regression tests for known-prohibited property paths.

data-hmmnm-seam="end">

Prabhu Kalyan Samal

Application Security Consultant at TCS. Certifications: CompTIA SecurityX, Burp Suite Certified Practitioner, Azure Security Engineer, Azure AI Engineer, Certified Red Team Operator, eWPTX v3, LPT, CompTIA PenTest+, Professional Cloud Security Engineer, SC-900, SC-200, PSPO I, CEH, Oracle Java SE 8, ISP, Six Sigma Green Belt, DELF, AutoCAD. Writing about ethical hacking, security tutorials, and tech education at Hmmnm.