CVE-2026-47850

Authorization Bypass
Affects
Spring Data REST
in
Spring
No items found.
Versions
>=5.1.0 <=5.1.0, >=5.0.0 <=5.0.6, >=4.5.0 <=4.5.13, >=4.0.0 <=4.4.15, >=3.1.0 <=3.7.20
Exclamation circle icon
Patch Available

This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.

Overview

Spring Data REST turns Spring Data repositories into hypermedia-driven REST resources, exporting each aggregate root as a collection and item resource with HTTP semantics for GET, POST, PUT, PATCH and DELETE, and mapping JSON request bodies onto the persisted domain objects without any hand-written controller code.

An Authorization Bypass vulnerability (CVE-2026-47850) has been identified in the PUT payload merging logic of Spring Data REST, which allows an authenticated client with PUT access to persist its own version value for an immutable aggregate, defeating the optimistic-locking protection the server is supposed to control.

Per OWASP: Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification, or destruction of all data or performing a business function outside the user's limits.

This issue affects the PUT payload merging of Spring Data REST, in applications that export a repository whose aggregate root is an immutable type, such as a Java record, a Kotlin data class or a type instantiated through an all-args persistence creator, or that permits a body-driven polymorphic subtype change, and that declares a version property visible to Jackson's deserialization model.

Details

Module Info

Vulnerability Info

This Medium-severity vulnerability is found in the org.springframework.data:spring-data-rest-webmvc package in the JSON payload merging layer of Spring Data REST.

Item resource PUT requests are handled by DomainObjectReader.readPut, which deserializes the client-supplied request body into a fresh object and then merges it onto the persisted instance:

// spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java
public <T> T readPut(final ObjectNode source, T target, final ObjectMapper mapper) throws Exception {

    Assert.notNull(source, "ObjectNode must not be null");
    Assert.notNull(target, "Existing object instance must not be null");
    Assert.notNull(mapper, "ObjectMapper must not be null");

    Object intermediate = mapper.readerFor(target.getClass()).readValue(source);
    return (T) mergeForPut(intermediate, target, mapper);
}

Nothing in this path restores the persisted identifier and version onto the incoming payload, so the object built from the request body carries whatever values the client sent. Whether those values survive is decided entirely by mergeForPut:

// spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java
<T> T mergeForPut(T source, T target, final ObjectMapper mapper) {

    Assert.notNull(mapper, "ObjectMapper must not be null");

    if (target == null || source == null) {
        return source;
    }

    boolean isTypeChange = !source.getClass().isInstance(target);

    boolean immutableTarget = entities.getPersistentEntity(target.getClass())
            .map(PersistentEntity::isImmutable)
            .orElse(true); // Not a Spring Data managed type -> no detailed merging

    return entities.getPersistentEntity(isTypeChange ? source.getClass() : target.getClass()) //
            .map(it -> {

                MappedProperties properties = MappedProperties.forDeserialization(it, mapper);

                if (isTypeChange || immutableTarget || it.isImmutable()) {

                    copyRemainingProperties(properties.getIgnoredProperties(), target, source);

                    return source;
                }

                MergingPropertyHandler propertyHandler = new MergingPropertyHandler(source, target, it, mapper);

                it.doWithProperties(propertyHandler);
                it.doWithAssociations(new LinkedAssociationSkippingAssociationHandler(associationLinks, propertyHandler));

                return target;

            }).orElse(source);
}

For a mutable aggregate the merge walks the persisted target property by property, and the handler that applies each value refuses to touch server-owned state:

// spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/DomainObjectReader.java (MergingPropertyHandler)
public void doWithPersistentProperty(PersistentProperty<?> property) {

    if (property.isIdProperty() || property.isVersionProperty() || !property.isWritable()) {
        return;
    }
    ...
}

For an immutable target, or when the request body selects a different polymorphic subtype, the method takes the early branch instead: it returns source, the object constructed from the request body, and copies back only the properties Jackson was told to ignore. The identifier and version guard above is never reached, because no merge onto the persisted instance happens at all. The identifier is re-applied later by the request-handling layer, but the version property is not, so a version supplied in the PUT body is written straight through to the store.

Because the version property is what Spring Data uses for optimistic locking, a client can send a stale or arbitrary version with an otherwise ordinary PUT and have it accepted. That silently discards the lost-update protection the aggregate declares, letting a stale write overwrite a concurrent update on applications that rely on the version property rather than mandatory If-Match and ETag preconditions. Only network access and an account permitted to issue PUT requests against the exported resource are required.

The last public release of the 4.5 line, 4.5.13, still lacks the identifier and version retention step, so that line is affected through its latest publicly available build.

Mitigation

Only recent versions of Spring Data REST receive community support. Older lines are End-of-Life and will not receive public updates to address this issue.

Users of the affected components should apply one of the following mitigations:

  • Upgrade to a currently supported version of Spring Data REST.
  • Leverage a commercial support partner like HeroDevs for post-EOL security support.

Credits

  • The published advisory for this issue does not credit an external finder.
Vulnerability Details
Severity
Level
CVSS Assessment
Low
>=0 <4
Medium
>=4 <6
High
>=6 <8
Critical
>=8 <10
Medium
ID
CVE-2026-47850
PROJECT Affected
Spring Data REST
Versions Affected
>=5.1.0 <=5.1.0, >=5.0.0 <=5.0.6, >=4.5.0 <=4.5.13, >=4.0.0 <=4.4.15, >=3.1.0 <=3.7.20
NES Versions Affected
Published date
August 26, 2026
≈ Fix date
August 28, 2026
Category
Authorization Bypass
Vex Document
Download VEXHow do I use it?
Sign up for the latest vulnerability alerts fixed in
NES for Spring
Rss feed icon
Subscribe via RSS
or

By submitting the form I acknowledge receipt of our Privacy Policy.

Thanks for signing up for our Newsletter! We look forward to connecting with you.
Oops! Something went wrong while submitting the form.