Skip to content

Commit bc1613c

Browse files
authored
Backport PR #5964 into 2.18 to fix #5962 (#6039)
1 parent ff5d313 commit bc1613c

4 files changed

Lines changed: 108 additions & 2 deletions

File tree

‎release-notes/CREDITS-2.x‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,4 +1910,7 @@ Omkhar Arasaratnam (@omkhar)
19101910
should validate element type
19111911
(2.18.8)
19121912
* Reported #5988: `PolymorphicTypeValidator` needs to validate generic type parameters too
1913-
(2.18.8)
1913+
(2.18.8)
1914+
* Contributed fix for #5962: Case-insensitive deserialization may use
1915+
wrong `@JsonIgnoreProperties`
1916+
(2.18.9)

‎release-notes/VERSION-2.x‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Project: jackson-databind
44
=== Releases ===
55
------------------------------------------------------------------------
66

7+
2.18.9 (not yet released)
8+
9+
#5962: Case-insensitive deserialization may use wrong `@JsonIgnoreProperties`
10+
[CVE-2026-54515]
11+
(fixed by Omkhar A)
12+
713
2.18.8 (28-May-2026)
814

915
#5950: Improve `UUIDDeserializer` error handling

‎src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,10 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
894894
// 16-May-2016, tatu: How about per-property case-insensitivity?
895895
Boolean B = format.getFeature(JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
896896
if (B != null) {
897-
BeanPropertyMap propsOrig = _beanProperties;
897+
// [databind#5962]: must rebuild from the (possibly filtered) contextual
898+
// BeanPropertyMap so that per-property @JsonIgnoreProperties exclusions
899+
// applied by _handleByNameInclusion() above are preserved.
900+
BeanPropertyMap propsOrig = contextual._beanProperties;
898901
BeanPropertyMap props = propsOrig.withCaseInsensitivity(B.booleanValue());
899902
if (props != propsOrig) {
900903
contextual = contextual.withBeanProperties(props);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.fasterxml.jackson.databind.deser.filter;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import com.fasterxml.jackson.annotation.JsonFormat;
6+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
7+
8+
import com.fasterxml.jackson.databind.*;
9+
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
10+
11+
import static org.junit.jupiter.api.Assertions.*;
12+
13+
/**
14+
* [databind#5962]: Case-insensitive BeanPropertyMap rebuild undoes per-property
15+
* {@code @JsonIgnoreProperties}.
16+
*
17+
* {@code BeanDeserializerBase.createContextual()} calls {@code _handleByNameInclusion()}
18+
* to filter properties according to per-property {@code @JsonIgnoreProperties}, producing
19+
* a contextual deserializer with the restricted {@code BeanPropertyMap}. However, the
20+
* subsequent case-insensitivity block read {@code _beanProperties} (the *original*
21+
* unfiltered map from {@code this}) rather than {@code contextual._beanProperties} (the
22+
* filtered map). {@code withCaseInsensitivity()} then rebuilt the map from the unfiltered
23+
* source, and {@code contextual.withBeanProperties(props)} overwrote the filtered map with
24+
* the unfiltered one — any properties removed by {@code _handleByNameInclusion} were
25+
* restored.
26+
*
27+
* Patch: source the case-insensitive rebuild from {@code contextual._beanProperties}.
28+
*/
29+
public class IgnorePropertiesCaseInsensitive5962Test extends DatabindTestUtil
30+
{
31+
static class AdminDto {
32+
public String adminKey = "DEFAULT";
33+
public String username;
34+
}
35+
36+
// Container that ignores "adminKey" on the AdminDto field AND enables case-insensitive matching
37+
static class Container {
38+
@JsonIgnoreProperties("adminKey")
39+
@JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
40+
public AdminDto admin;
41+
}
42+
43+
// Baseline container: only @JsonIgnoreProperties, no case-insensitive format override
44+
static class BaselineContainer {
45+
@JsonIgnoreProperties("adminKey")
46+
public AdminDto admin;
47+
}
48+
49+
/**
50+
* NEGATIVE CONTROL: without the @JsonFormat case-insensitive override, @JsonIgnoreProperties
51+
* correctly suppresses adminKey on the nested AdminDto field.
52+
*/
53+
@Test
54+
public void test5962_negativeControl_withoutCaseInsensitivity() throws Exception {
55+
ObjectMapper mapper = jsonMapperBuilder().build();
56+
String json = "{\"admin\":{\"adminKey\":\"HACKED\",\"username\":\"alice\"}}";
57+
BaselineContainer result = mapper.readValue(json, BaselineContainer.class);
58+
// Without case-insensitive format, @JsonIgnoreProperties blocks adminKey
59+
assertNotEquals("HACKED", result.admin.adminKey,
60+
"@JsonIgnoreProperties alone (no case-insensitive format) should block adminKey");
61+
assertEquals("alice", result.admin.username);
62+
}
63+
64+
/**
65+
* EXPLOIT PATH: the case-insensitive BeanPropertyMap rebuild (triggered by
66+
* @JsonFormat ACCEPT_CASE_INSENSITIVE_PROPERTIES) restores the unfiltered original
67+
* _beanProperties, undoing the @JsonIgnoreProperties("adminKey") exclusion.
68+
* Case-insensitive matching then routes "adminKey" (or "ADMINKEY") to the setter.
69+
*
70+
* Security assertion: adminKey must NOT be settable via JSON when the enclosing
71+
* container declares @JsonIgnoreProperties("adminKey") on the field.
72+
*/
73+
@Test
74+
public void test5962_caseInsensitiveRebuildRestoresIgnoredProperty() throws Exception {
75+
ObjectMapper mapper = jsonMapperBuilder()
76+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
77+
.build();
78+
79+
// Exact case — should be blocked by @JsonIgnoreProperties
80+
String json = "{\"admin\":{\"adminKey\":\"HACKED\",\"username\":\"alice\"}}";
81+
Container result = mapper.readValue(json, Container.class);
82+
assertNotEquals("HACKED", result.admin.adminKey,
83+
"[databind#5962]: case-insensitive BeanPropertyMap rebuild restored 'adminKey' " +
84+
"after it was removed by @JsonIgnoreProperties. The property was set to 'HACKED'.");
85+
assertEquals("alice", result.admin.username);
86+
87+
// Mixed case — exploits the case-insensitive rebuild more directly
88+
String jsonMixed = "{\"admin\":{\"AdminKey\":\"HACKED2\",\"username\":\"bob\"}}";
89+
Container result2 = mapper.readValue(jsonMixed, Container.class);
90+
assertNotEquals("HACKED2", result2.admin.adminKey,
91+
"[databind#5962]: 'AdminKey' (mixed case) matched 'adminKey' via case-insensitive " +
92+
"lookup that was rebuilt from the unfiltered property map.");
93+
}
94+
}

0 commit comments

Comments
 (0)