Safeguard
Vulnerability Analysis

Spring4Shell (CVE-2022-22965) Explained: RCE Through Spring Data Binding

CVE-2022-22965, Spring4Shell, let attackers write a JSP web shell to Spring MVC apps on JDK 9+ by abusing data binding. Here is the ClassLoader trick and the exact conditions required.

Marcus Chen
Security Researcher
5 min read

CVE-2022-22965, dubbed Spring4Shell, is a remote code execution vulnerability in the Spring Framework, rated CVSS v3.1 9.8 (Critical). Under the right (but common) deployment conditions, an unauthenticated attacker could abuse Spring's request-parameter data binding to write a JSP web shell into a running application's web root and then execute arbitrary commands. Its name deliberately echoes Log4Shell, though the two are unrelated bugs — and Spring4Shell also should not be confused with CVE-2022-22963, a separate Spring Cloud Function SpEL flaw disclosed the same week.

Timeline and impact

Details leaked ahead of a coordinated release, and the Spring team published fixed versions on March 31, 2022, amid considerable confusion. Early reporting conflated several vulnerabilities, and the exploitation prerequisites were widely misunderstood, which led some teams to panic-patch and others to wrongly conclude they were safe. The genuinely exploitable configuration was specific but far from rare: a Spring MVC or WebFlux application, running on JDK 9 or later, packaged as a traditional WAR deployed on Apache Tomcat, using Spring's data binding on plain Java objects. Where those lined up, public exploits produced a reliable web shell within hours of disclosure.

Much of the initial noise came from mixing Spring4Shell up with CVE-2022-22963, a completely separate SpEL-injection flaw in Spring Cloud Function that was disclosed the same week and also enabled RCE. They share nothing beyond the "Spring" name and the timing, yet a lot of early triage treated them as one event. That confusion is instructive: when two critical advisories against the same vendor land within days, teams without a precise component inventory cannot tell which of their services is affected by which bug, and they end up either patching blindly or freezing. The organizations that handled the week calmly were the ones that could answer, per service, exactly which Spring artifacts and versions were on the classpath.

Technical root cause

Spring's data binding automatically maps HTTP request parameters onto the fields of a command object using nested property paths. Sending user.address.city=... sets the city property on the nested address object. The problem: binding can also walk into an object's Java internals.

Years earlier, CVE-2010-1622 patched a similar issue by blocking access to the class.classLoader property during binding. But Java 9 introduced the module system, and with it Class.getModule(). That added a fresh reachable path — class.module.classLoader — that the old denylist did not cover. Attackers used that path to reach Tomcat's classloader and reconfigure its logging valve:

class.module.classLoader.resources.context.parent.pipeline.first.pattern=<jsp payload>
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

By binding these parameters, an attacker reprogrammed Tomcat's AccessLogValve to write a file whose contents (the "pattern") were attacker-controlled JSP, into the public web root, with a .jsp suffix. The next request to shell.jsp executed the payload. The whole exploit is just carefully chosen parameter names — no upload endpoint, no deserialization, no memory corruption. The JDK 9+ requirement is exactly because the attack hinges on the module property that only exists there.

How to detect if you are affected

  • Version check. Vulnerable Spring Framework releases include the 5.3.0–5.3.17 and 5.2.0–5.2.19 lines and older. Fixed in 5.3.18 and 5.2.20. Spring Boot pulled the fix in via its dependency management updates.
  • Check the deployment shape, not just the version. The classic exploit needs JDK 9+, a WAR on Tomcat, and Spring MVC/WebFlux with data binding. An executable-JAR Spring Boot app on embedded Tomcat was not exploitable via the original vector, though patching remains correct.
  • Inspect logs and the web root for unexpected .jsp files and request parameters containing class.module.classLoader.

Deployment nuance is exactly why version-only tools mislead here. Safeguard's software composition analysis pins the exact spring-webmvc/spring-core version across your services and packaged artifacts.

Remediation and patched versions

  1. Upgrade to Spring Framework 5.3.18 / 5.2.20 or later, or to a Spring Boot release that pulls those in. The fix restricts which properties data binding is allowed to touch.
  2. Rebuild WARs and images. A patched dependency in your repo is inert until the artifact is rebuilt. Safeguard's container security scanning flags a vulnerable Spring version inside an image layer.
  3. Interim mitigation: if you cannot upgrade immediately, a @ControllerAdvice that disallows binding of the class.* field patterns was the widely used stopgap. Prefer the real upgrade.
  4. Verify by re-scanning after rollout; multi-service estates are where a lone unpatched instance survives.

How Safeguard surfaces and auto-fixes Spring4Shell

Spring4Shell's chaos came from ambiguity — teams could not quickly answer "which of our apps are actually exposed?" Safeguard resolves that with a continuously updated dependency graph enriched by Griffin AI, which reasons about the exploit's real prerequisites and reachability rather than firing on version match alone, so you separate genuinely exposed WAR deployments from apps that carry the dependency but not the risk. Findings are ranked with CISA KEV and EPSS signals so an internet-facing, actively exploited RCE outranks routine noise.

When a safe upgrade exists, automated fix pull requests raise the coordinated version bump across your services and run your test suite, turning a framework-wide upgrade from a multi-day scramble into a reviewed PR. If you are comparing how modern SCA handles ecosystem-wide Java events versus older scanners, our feature comparison covers it directly.

The bug itself was subtle; the operational lesson was not. Fast, accurate inventory plus automated patching is what turns the next Spring4Shell from a fire drill into a routine merge.

Get started at app.safeguard.sh/register, or explore the docs at docs.safeguard.sh.

Never miss an update

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