In short

Spring4Shell is the community's name for CVE-2022-22965, a remote code execution flaw in Spring Framework disclosed on 31 March 2022. Spring's data binding, which copies request parameters onto an object's properties, could reach the application's class loader on JDK 9 and later; the reported exploit needed the application packaged as a WAR on Apache Tomcat. It was fixed in Spring Framework 5.3.18 and 5.2.20, after details leaked online before the release. The lesson is mass assignment at framework level: bind only the fields a form declares, and upgrade frameworks on a supported line rather than relying on denylist workarounds.

  • Data binding treats request parameter names as property paths, so a parameter can reach nested properties the developer never meant to expose.
  • The reported exploit needed JDK 9+, a WAR deployed on a standalone Servlet container such as Tomcat, and spring-webmvc or spring-webflux; Spring Boot executable jars were not vulnerable to it, but Spring warned the flaw was more general.
  • Fixed in Spring Framework 5.3.18 and 5.2.20 (Spring Boot 2.6.6 and 2.5.12); Tomcat 10.0.20, 9.0.62 and 8.5.78 closed the vector on Tomcat's side.
  • The disallowedFields denylist workaround had loopholes; a follow-up, CVE-2022-22968, showed its patterns were case sensitive.
  • In October 2026 only Spring Framework 7.0.x gets open-source fixes: 5.3.x ended in August 2024 and 6.2.x in June 2026.
  • The specific exploit path and payloads come from third-party write-ups; Spring's own posts describe ClassLoader access in general terms.

Incident at a glance

Dates
to
Category
Vulnerability exploitation
Organizations
  • Spring (VMware)
  • Apache Tomcat
Impact
Spring MVC and WebFlux applications on JDK 9+ were open to remote code execution through data binding; the reported exploit needed a WAR on Tomcat. Details leaked before the fix, and CISA listed it as exploited.
How it was found
Reported privately to VMware by codeplutos and meizjm3i of AntGroup FG Security Lab; details then leaked online before the fix.

Timeline

  1. The flaw is reported to VMware.
  2. Details leak online while the fix is in progress.
  3. Spring Framework 5.3.18 and 5.2.20 and CVE-2022-22965 are published.
  4. Tomcat 10.0.20, 9.0.62 and 8.5.78 close the vector on Tomcat's side.
  5. CISA adds CVE-2022-22965 to the KEV catalog.
  6. CVE-2022-22968 shows the disallowedFields workaround is case sensitive.

Verify it

  1. Generate an SBOM for every Java service and confirm no spring-beans, spring-webmvc or spring-webflux below 5.3.18 or 5.2.20 remains, then confirm each service is on a Spring Framework line that still gets fixes.
  2. List services deployed as WAR files on standalone Tomcat, Payara or GlassFish and check their container versions too.
  3. For each controller that binds request parameters to an object, confirm the object is a dedicated request type with only the fields the form needs, or that the binder has an allowlist.
  4. Add a test that posts an extra parameter (such as a privileged field) and asserts it is not bound.
  5. Check that no controller overrides a global binder configuration with its own @InitBinder that drops the restriction.
Applies to
  • Spring Framework (spring-webmvc, spring-webflux) Vulnerable: 5.3.0 to 5.3.17, 5.2.0 to 5.2.19 and older on JDK 9+; fixed: 5.3.18 and 5.2.20; supported in October 2026: 7.0.x
  • Apache Tomcat as the Servlet container 10.0.19, 9.0.61, 8.5.77 and earlier known vulnerable; 10.0.20, 9.0.62 and 8.5.78 close the vector

Java applications built on Spring MVC or Spring WebFlux that bind request parameters to objects, and teams that respond to vulnerabilities in widely used frameworks.

Part of
Workflow: Respond to a vulnerability. Collection: case studies

Fix it in your stack

Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.

Learning goals and prerequisitesAbout 28 minutes: 10 to read and 18 for the exercise

What you will learn

  • Explain how Spring's data binding let request parameters reach class-loader properties on JDK 9 and later.
  • Decide whether a given deployment matched the reported exploit's conditions, and why that is not the same as safe.
  • Replace denylist binding workarounds with allowlists or dedicated request types, and test them.
  • Plan framework upgrades so a critical fix lands on a supported line.

Before you start

Java and Spring developers, and AppSec and platform teams who triage and roll out framework fixes across many services

  • Know how Spring MVC binds request parameters to a @ModelAttribute object.
  • Know what mass assignment is.
  • For the code: Python 3 for the SBOM check; JDK 17 or later and Spring Framework 7 for the binder example.

Refresh first: Authorize functions and fields as well as objects

Go to the guided practice

How a request parameter reached the class loader

Application on JDK 9+HTTP clientSpring databinderForm objectTomcat classloaderParameters named likeproperty paths1Set declared fields1Follow class. intoClass2Set class-loader properties3Server behaviorchanged4Trust boundaryUntrusted input1Step, described below
  1. Bind the requestA controller takes a form object, and Spring copies each request parameter onto the property path its name describes, such as address.city.
  2. Walk past the formEvery Java object has a getClass() method, so a parameter name starting with class. walks from the form into the Class object and on to the objects it exposes.
  3. Miss the new pathSpring filtered Class.classLoader by name. On JDK 9 and later, Class has other getters, and the filter did not stop a path that reached a ClassLoader another way.
  4. Change the containerOn Tomcat, the class loader of a WAR application exposes properties that change how the server behaves, which third-party write-ups show being used to write a file and run code.
Binding treats parameter names as paths into your object graph. An allowlist of fields stops the walk at the first step.

A framework feature that let parameters reach the class loader

On 31 March 2022 the Spring team announced 'an RCE vulnerability in the Spring Framework that was leaked out ahead of CVE publication.' It had been reported to VMware two days earlier by codeplutos and meizjm3i of AntGroup FG Security Lab; while the team worked on emergency releases, 'details were leaked in full detail online'. Spring Framework 5.3.18 and 5.2.20 shipped the same day as CVE-2022-22965, rated critical. The community called it Spring4Shell, echoing Log4Shell from December 2021; Spring's own posts do not use the name.

The flaw was in data binding, the feature that fills a Java object from request parameters. Spring's advisory: 'A Spring MVC or Spring WebFlux application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. The specific exploit requires the application to run on Tomcat as a WAR deployment.' A Spring Boot executable jar, the default, 'is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.'

That combination, a critical bug in one of the most used Java frameworks whose exploitability depended on deployment details, made it a hard triage problem. CISA added it to the Known Exploited Vulnerabilities catalog on 4 April 2022.

Timeline

Timeline
WhenEventSource
2022-03-29The issue is reported to VMware late on Tuesday evening (GMT) by codeplutos and meizjm3i of AntGroup FG.Spring early announcement
2022-03-30While Spring works on a fix, details are leaked in full online.Spring early announcement
2022-03-31Spring publishes an early announcement, then Spring Framework 5.3.18 and 5.2.20, Spring Boot 2.6.6 and 2.5.12, and CVE-2022-22965. The fix commit refines property filtering in CachedIntrospectionResults.Spring blog and advisory; spring-framework commit 002546b
2022-04-01Spring publishes a mitigation alternative: Apache Tomcat 10.0.20, 9.0.62 and 8.5.78 close the attack vector on Tomcat's side.Spring, Mitigation Alternative
2022-04-04CISA adds CVE-2022-22965 to the KEV catalog.CISA KEV
2022-04-08Spring notes an additional attack vector for GlassFish and Payara reported by Snyk.Spring early announcement, updates
2022-04-13CVE-2022-22968: the disallowedFields patterns used in the suggested workaround are case sensitive; fixed in 5.3.19 and 5.2.21.Spring advisory CVE-2022-22968
2024-08Open-source support for Spring Framework 5.3.x ends.Spring Framework versions wiki

Data binding is mass assignment by design

Spring's announcement explains the mechanism in one line: 'The issue relates to data binding used to populate an object from request parameters (either query parameters or form data).' Binding applies to controller parameters 'annotated with @ModelAttribute or optionally without it, and without any other Spring Web annotation.' A parameter named address.city sets getAddress().setCity(...): the name is a path through the object's properties. That is convenient, and it means the client chooses which properties to set.

Every Java object has a getClass() method, which the bean machinery sees as a property called class, so a path that starts with class. leaves the form object and walks into the Class object. Spring already blocked the dangerous step: before the fix, CachedIntrospectionResults ignored the classLoader and protectionDomain properties of Class by name. The fix commit, 'Refine PropertyDescriptor filtering', replaced that with two broader rules: on Class, allow only name and its variants; anywhere, ignore every property whose type is a ClassLoader or ProtectionDomain. The JDK 9 condition fits that change: JDK 9 gave Class new getters, including getModule(), and Module has its own getClassLoader(). Third-party exploit write-ups describe exactly that route, through class.module.classLoader; Spring's own posts say only that 'The vulnerability involves ClassLoader access and depends on the actual Servlet Container in use.'

This is the same weakness as mass assignment (CWE-915) in application code, one level down: the framework, not the developer, decided which properties were reachable. The application-level guide to mass assignment (/domains/appsec/authorization/function-property-authorization/) and the Spring mass-assignment fix card (/fix/mass-assignment/java-spring/) apply the same remedy, an explicit allowlist of bindable fields.

Why exploitability depended on how the app was deployed

Spring listed the requirements for the reported scenario: JDK 9 or higher; packaged as a traditional WAR and deployed on a standalone Servlet container; spring-webmvc or spring-webflux; and an affected Spring Framework version. 'Typical Spring Boot deployments using an embedded Servlet container or reactive web server are not impacted.' Tomcat 10.0.19, 9.0.61, 8.5.77 and earlier were known to be vulnerable, as were Payara and GlassFish, and 'Other Servlet containers may also be vulnerable.'

That made triage harder, not easier. A team with hundreds of services had to know, for each one, the JDK, the packaging, the container and the binding style, and the answer 'Boot jar, so not vulnerable to this exploit' came with Spring's caveat that other exploitation paths might exist. With an SBOM and deployment metadata per service, that is a query; without them, it means reading build files and deployment scripts service by service. The dependency response guide (/domains/appsec/vulnerability-response/dependency-vulnerability-response/) covers the workflow, and the Log4Shell case study (/domains/appsec/vulnerability-response/case-study-log4shell/) the same race four months earlier.

Why the denylist workaround leaked

For teams that could not upgrade at once, Spring offered three workarounds: upgrade Tomcat, downgrade to Java 8, or set disallowedFields on every WebDataBinder globally with the patterns class.*, Class.*, *.class.* and *.Class.*. Spring itself warned that the global setting 'may leave some loopholes, in particular if a controller sets disallowedFields locally through its own @InitBinder method, which overrides the global setting', and gave a more fail-safe variant that extends RequestMappingHandlerAdapter.

Two weeks later CVE-2022-22968 showed another gap: the patterns 'are case sensitive', so a field is protected only if both the upper- and lower-case first letter are listed, including in nested paths. A denylist has to anticipate every spelling of every dangerous path; an allowlist only has to list the fields the form needs. The Tomcat upgrade was likewise described as 'a tactical solution, and the main goal should be to upgrade to a currently supported Spring Framework version as soon as possible.'

Find exposed services from their SBOMs

The script reads a CycloneDX JSON SBOM and flags spring-beans, spring-webmvc and spring-webflux below the fixed versions, Tomcat below the releases that closed the vector, and any Spring Framework line that no longer gets open-source fixes. Pass --war for services packaged as WAR files, which matched the reported exploit's conditions. It ran with Python 3.14 on two sample SBOMs: one with Spring 5.3.17 and Tomcat 9.0.60, which it flags, and one with Spring 7.0.8, which it passes. Update OSS_SUPPORTED from Spring's versions page when you use it.

Testspring4shell_sbom.pyPython
"""Check a CycloneDX JSON SBOM for CVE-2022-22965 (Spring4Shell) exposure.

Flags spring-beans, spring-webmvc and spring-webflux below 5.2.20 / 5.3.18,
Tomcat below 8.5.78 / 9.0.62 / 10.0.20, and Spring Framework lines that no
longer get open-source fixes. Usage: spring4shell_sbom.py sbom.json [--war]
"""
import json
import re
import sys

SPRING = {"spring-beans", "spring-webmvc", "spring-webflux"}
SPRING_FIXED = {(5, 2): (5, 2, 20), (5, 3): (5, 3, 18)}
TOMCAT_FIXED = {(8, 5): (8, 5, 78), (9, 0): (9, 0, 62), (10, 0): (10, 0, 20)}
OSS_SUPPORTED = {(7, 0)}  # check the Spring Framework versions wiki when you run this


def parse(version):
    return tuple(int(n) for n in re.findall(r"\d+", version)[:3])


def findings(sbom, war):
    for c in sbom.get("components", []):
        group, name, v = c.get("group", ""), c.get("name", ""), parse(c.get("version", "0"))
        if group == "org.springframework" and name in SPRING:
            fixed = SPRING_FIXED.get(v[:2])
            if v[:2] < (5, 2) or (fixed and v < fixed):
                yield f"{name} {c['version']}: vulnerable to CVE-2022-22965" + (
                    " (packaged as a WAR, as the reported exploit requires)" if war else "")
            if v[:2] not in OSS_SUPPORTED:
                yield f"{name} {c['version']}: line {v[0]}.{v[1]} gets no open-source fixes; upgrade"
        if group.startswith("org.apache.tomcat") and name in ("tomcat-embed-core", "tomcat-catalina"):
            fixed = TOMCAT_FIXED.get(v[:2])
            if fixed and v < fixed:
                yield f"{name} {c['version']}: below the Tomcat release that closes the vector"


if __name__ == "__main__":
    with open(sys.argv[1], encoding="utf-8") as f:
        found = list(findings(json.load(f), "--war" in sys.argv))
    print("\n".join(found) or "no Spring4Shell exposure found")
    sys.exit(1 if found else 0)

Bind only the fields a form declares

Upgrading is the fix. As defense in depth, give each form a binder allowlist, or bind to a dedicated request type that has only the fields the client may set. The advice below applies setAllowedFields to one named model attribute. A JUnit test bound email, admin and class.name to the form with this configuration: only email was set, and the binding result listed admin and class.name as suppressed fields. It compiled and passed against Spring Framework 7.0.8 on JDK 21. Name the model attribute in @InitBinder so the allowlist applies only to the form it was written for.

FixedBindingAllowlist.javaJava
package example;

import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;

// Defense in depth, not a substitute for a fixed Spring Framework:
// bind only the fields each form declares, so request parameters can never
// reach other properties, nested objects or framework internals.
@ControllerAdvice
public class BindingAllowlist {

    @InitBinder("signupForm")
    public void signupFields(WebDataBinder binder) {
        binder.setAllowedFields("email", "name");
    }
}

Which controls would have stopped or limited it?

Which controls would have stopped or limited it?
ControlWhoSSDF practice or standardGuide
Restrict binding by type and path in the framework; review every reachable property when the platform adds APIs (JDK 9 here)FrameworkSSDF PW.1.1, PW.7.2/domains/appsec/secure-coding/framework-security-patterns/
Look for the whole class after a fix, as CVE-2022-22968 did for the workaroundFrameworkSSDF RV.3.3/domains/appsec/vulnerability-response/coordinated-disclosure/
Allowlist bindable fields, or bind to dedicated request typesApplication teamSSDF PW.5.1; CWE-915/domains/appsec/authorization/function-property-authorization/
An SBOM per service plus deployment metadata (JDK, packaging, container)Application teamSSDF PW.4.4, PS.3.2/domains/supply-chain/sbom/generate-release-inventory/
Stay on a framework line that still gets fixes, and rehearse emergency upgradesApplication teamSSDF PW.4.1, RV.2.2/domains/appsec/vulnerability-response/dependency-vulnerability-response/
Prioritize by KEV status and exposure, not CVSS aloneApplication teamSSDF RV.2.1; CISA KEV/frameworks/vulnerability-prioritization/

Lessons that still apply

  • Binding is mass assignment by design. Decide which fields a request may set, per form, and enforce it with an allowlist or a dedicated type.
  • A denylist of dangerous paths is a race against spellings; CVE-2022-22968 lost it within two weeks.
  • Exploitability that depends on deployment details makes triage slower unless you already know each service's JDK, packaging and container.
  • Patch the framework, not just the container: the Tomcat fix closed one route, and Spring warned of others.
  • Being on a supported line is the precondition for a quick fix. In October 2026 that means Spring Framework 7.0.x for open-source support.

What the public evidence does not establish

Spring's posts do not describe the exploit path or payloads; the route through class.module.classLoader and the use of Tomcat's logging configuration come from third-party exploit write-ups, consistent with the fix commit but not confirmed by Spring. The public record does not establish how widely CVE-2022-22965 was exploited or by whom, beyond CISA's KEV listing of known exploitation. Whether deployments outside Tomcat, Payara and GlassFish, or Spring Boot executable jars, could be exploited in practice is not established either way; Spring's statement that 'there may be other ways to exploit it' is a caution, not a finding.

Guided practice

It is 1 April 2022. You own five Java services. Decide each one's exposure and action. Treat the facts as true.

Supplied scenario material
Plain text
S1: Spring Framework 5.3.16, packaged as a WAR on standalone Tomcat 9.0.58, JDK 11; controllers bind forms with @ModelAttribute.
S2: Spring Boot 2.6.4 (Spring Framework 5.3.16), executable jar with embedded Tomcat, JDK 17.
S3: Spring Framework 5.3.16, WAR on Tomcat 9.0.58, JDK 8.
S4: Spring Framework 5.3.18, WAR on Tomcat 9.0.58, JDK 11; one controller has its own @InitBinder that calls setDisallowedFields("internalId").
S5: Spring Framework 4.3.30 (end of life), WAR on Tomcat 8.5.70, JDK 11; the team says upgrading Spring will take a quarter.
  1. 1. Decide S1

    Is S1 exposed, and what do you do?

    Expected result and reasoning

    Yes: it meets every condition of the reported exploit (affected version, JDK 9+, WAR on Tomcat, data binding). Upgrade to 5.3.18 today; if that takes hours, upgrade Tomcat to 9.0.62 first and apply an allowlist or the global workaround in the meantime.

  2. 2. Decide S2

    S2 is a Boot jar. Can you leave it?

    Expected result and reasoning

    It is not vulnerable to the reported exploit, but it runs an affected version and Spring warned of other possible paths. Upgrade to Spring Boot 2.6.6 on the normal fast track, after S1 and S5.

  3. 3. Decide S3

    S3 runs on JDK 8. Is it safe?

    Expected result and reasoning

    Not vulnerable to this issue as described, since it needs JDK 9+. Still upgrade Spring, and note that a JDK upgrade later would expose it if Spring were not upgraded first.

  4. 4. Decide S4

    S4 is on the fixed version. Anything left?

    Expected result and reasoning

    CVE-2022-22965 is fixed. The local setDisallowedFields shows the pattern Spring warned about, a controller overriding global binder settings; replace denylists with allowlists, and plan 5.3.19 for CVE-2022-22968.

  5. 5. Decide S5

    What do you do with S5?

    Expected result and reasoning

    It is on an end-of-life Spring line with no fix and meets the exploit conditions. Upgrade Tomcat to 8.5.78 now, add binder allowlists, restrict exposure, and treat the Spring migration as urgent rather than a quarter-long project.

Your deliverable: A table of S1 to S5 with exposed (yes, no or not to this exploit), the action, and its deadline.

The fixture uses the conditions Spring published for the reported exploit. Spring cautioned that other exploitation paths may exist, so 'not vulnerable to this exploit' is not the same as 'safe to leave unpatched'.

Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.

No draft saved on this device yet.

Sources & further reading

The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.

  1. Spring blog: Spring Framework RCE, early announcement. 31 March 2022, updated through 13 April 2022.
  2. Spring security advisory: CVE-2022-22965, Spring Framework RCE via data binding on JDK 9+. 31 March 2022.
  3. Spring blog: Spring Framework RCE, mitigation alternative. 1 April 2022.
  4. Spring security advisory: CVE-2022-22968, Spring Framework data binding rules vulnerability. 13 April 2022.
  5. spring-framework commit 002546b: Refine PropertyDescriptor filtering. 31 March 2022.
  6. NVD: CVE-2022-22965
  7. CISA Known Exploited Vulnerabilities catalog. Catalog version 2026.10.08.
  8. Spring Framework wiki: Spring Framework versions and support

Reference information

CAPEC attack patterns

Download this guide as Markdown