Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ Omkhar Arasaratnam (@omkhar)
[3.1.4]
* Contributed fix for #5957: Improve `java.time.Month` deserialization validation
[3.1.4]
* Contributed fix for #5962: Case-insensitive deserialization may use
wrong `@JsonIgnoreProperties`
[3.1.4]

Michael Orzechowski (@MikeBlink)
* Reported #5941: `MapperBuilder.addModule()` does not recursively register transitive
Expand Down
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ No changes since 3.1
(reported, fix suggested by Omkhar A)
#5957: Improve `java.time.Month` deserialization validation
(fix by Omkhar A)
#5962: Case-insensitive deserialization may use wrong `@JsonIgnoreProperties`
(fixed by Omkhar A)

3.1.3 (01-May-2026)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,10 @@ public ValueDeserializer<?> createContextual(DeserializationContext ctxt,
// 16-May-2016, tatu: How about per-property case-insensitivity?
Boolean B = format.getFeature(JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
if (B != null) {
BeanPropertyMap propsOrig = _beanProperties;
// [databind#5962]: must rebuild from the (possibly filtered) contextual
// BeanPropertyMap so that per-property @JsonIgnoreProperties exclusions
// applied by _handleByNameInclusion() above are preserved.
BeanPropertyMap propsOrig = contextual._beanProperties;
BeanPropertyMap props = propsOrig.withCaseInsensitivity(B.booleanValue());
if (props != propsOrig) {
contextual = contextual.withBeanProperties(props);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package tools.jackson.databind.deser.filter;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import tools.jackson.databind.*;
import tools.jackson.databind.testutil.DatabindTestUtil;

import static org.junit.jupiter.api.Assertions.*;

/**
* [databind#5962]: Case-insensitive BeanPropertyMap rebuild undoes per-property
* {@code @JsonIgnoreProperties}.
*
* {@code BeanDeserializerBase.createContextual()} calls {@code _handleByNameInclusion()}
* to filter properties according to per-property {@code @JsonIgnoreProperties}, producing
* a contextual deserializer with the restricted {@code BeanPropertyMap}. However, the
* subsequent case-insensitivity block read {@code _beanProperties} (the *original*
* unfiltered map from {@code this}) rather than {@code contextual._beanProperties} (the
* filtered map). {@code withCaseInsensitivity()} then rebuilt the map from the unfiltered
* source, and {@code contextual.withBeanProperties(props)} overwrote the filtered map with
* the unfiltered one — any properties removed by {@code _handleByNameInclusion} were
* restored.
*
* Patch: source the case-insensitive rebuild from {@code contextual._beanProperties}.
*/
public class IgnorePropertiesCaseInsensitive5962Test extends DatabindTestUtil
{
static class AdminDto {
public String adminKey = "DEFAULT";
public String username;
}

// Container that ignores "adminKey" on the AdminDto field AND enables case-insensitive matching
static class Container {
@JsonIgnoreProperties("adminKey")
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
public AdminDto admin;
}

// Baseline container: only @JsonIgnoreProperties, no case-insensitive format override
static class BaselineContainer {
@JsonIgnoreProperties("adminKey")
public AdminDto admin;
}

/**
* NEGATIVE CONTROL: without the @JsonFormat case-insensitive override, @JsonIgnoreProperties
* correctly suppresses adminKey on the nested AdminDto field.
*/
@Test
public void test5962_negativeControl_withoutCaseInsensitivity() throws Exception {
ObjectMapper mapper = jsonMapperBuilder().build();
String json = "{\"admin\":{\"adminKey\":\"HACKED\",\"username\":\"alice\"}}";
BaselineContainer result = mapper.readValue(json, BaselineContainer.class);
// Without case-insensitive format, @JsonIgnoreProperties blocks adminKey
assertNotEquals("HACKED", result.admin.adminKey,
"@JsonIgnoreProperties alone (no case-insensitive format) should block adminKey");
assertEquals("alice", result.admin.username);
}

/**
* EXPLOIT PATH: the case-insensitive BeanPropertyMap rebuild (triggered by
* @JsonFormat ACCEPT_CASE_INSENSITIVE_PROPERTIES) restores the unfiltered original
* _beanProperties, undoing the @JsonIgnoreProperties("adminKey") exclusion.
* Case-insensitive matching then routes "adminKey" (or "ADMINKEY") to the setter.
*
* Security assertion: adminKey must NOT be settable via JSON when the enclosing
* container declares @JsonIgnoreProperties("adminKey") on the field.
*/
@Test
public void test5962_caseInsensitiveRebuildRestoresIgnoredProperty() throws Exception {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by GHSA-5jmj-h7xm-6q6v, this test fails for me with Jackson 2.21.4, suggesting that version is also affected, yet the advisory says only >= 3.1.0, < 3.1.4 is affected. What am I missing here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue was specifically reported against 3.1 -- unlike other problems that included 2.21 (and often 2.18). I assumed reported had analyzed these cases; perhaps they did not?

If test can be made to fail against 2.21 branch (or maybe even 2.18), I'd be happy to address there too. Note: PR against 2.18 is likely forward mergeable to 2.21 so one pr would be fine.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sonatype now flags it: #5962 (comment)

ObjectMapper mapper = jsonMapperBuilder().build();

// Exact case — should be blocked by @JsonIgnoreProperties
String json = "{\"admin\":{\"adminKey\":\"HACKED\",\"username\":\"alice\"}}";
Container result = mapper.readValue(json, Container.class);
assertNotEquals("HACKED", result.admin.adminKey,
"[databind#5962]: case-insensitive BeanPropertyMap rebuild restored 'adminKey' " +
"after it was removed by @JsonIgnoreProperties. The property was set to 'HACKED'.");
assertEquals("alice", result.admin.username);

// Mixed case — exploits the case-insensitive rebuild more directly
String jsonMixed = "{\"admin\":{\"AdminKey\":\"HACKED2\",\"username\":\"bob\"}}";
Container result2 = mapper.readValue(jsonMixed, Container.class);
assertNotEquals("HACKED2", result2.admin.adminKey,
"[databind#5962]: 'AdminKey' (mixed case) matched 'adminKey' via case-insensitive " +
"lookup that was rebuilt from the unfiltered property map.");
}
}
Loading