Skip to content

Commit 08c95f2

Browse files
committed
sec: fix: fix RCE found in InfraEvent
1 parent a9f52c4 commit 08c95f2

File tree

4 files changed

+428
-78
lines changed

4 files changed

+428
-78
lines changed
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package com.example.arInfra.config;
2+
3+
import static org.owasp.encoder.Encode.forJava;
4+
5+
import com.example.arInfra.InfraGenerated;
6+
import com.fasterxml.jackson.annotation.JsonInclude;
7+
import com.fasterxml.jackson.core.JsonParser;
8+
import com.fasterxml.jackson.core.json.JsonReadFeature;
9+
import com.fasterxml.jackson.databind.DeserializationFeature;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
12+
import com.fasterxml.jackson.databind.SerializationFeature;
13+
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
14+
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
15+
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
16+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
17+
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
18+
import java.util.List;
19+
import lombok.extern.slf4j.Slf4j;
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.context.annotation.Primary;
23+
24+
/**
25+
* Jackson ObjectMapper configuration with security controls.
26+
*
27+
* <p>This configuration provides a production-ready ObjectMapper with security hardening to prevent
28+
* common JSON deserialization vulnerabilities while maintaining flexibility for application
29+
* development.
30+
*
31+
* <p><b>Security Features:</b>
32+
*
33+
* <ul>
34+
* <li><b>Polymorphic Type Validation:</b> Allowlist-based validation prevents arbitrary class
35+
* instantiation (prevents RCE via deserialization gadgets)
36+
* <li><b>No Default Typing:</b> Default typing is explicitly disabled to prevent injection
37+
* attacks
38+
* <li><b>Strict Number Handling:</b> Prevents integer overflow and floating-point precision
39+
* issues
40+
* <li><b>Content Validation:</b> Validates JSON structure and prevents malformed input
41+
* </ul>
42+
*
43+
* <p><b>Functionality Features:</b>
44+
*
45+
* <ul>
46+
* <li><b>Java Time Support:</b> Native handling of java.time.* types (LocalDateTime, Instant,
47+
* etc.)
48+
* <li><b>JDK 8 Support:</b> Optional types (Optional&lt;T&gt;)
49+
* <li><b>Parameter Names:</b> Preserves parameter names for cleaner JSON binding
50+
* <li><b>Flexible Deserialization:</b> Backward compatibility with unknown properties
51+
* <li><b>Null Handling:</b> Configurable null and empty value handling
52+
* <li><b>Date Formats:</b> ISO-8601 standardized date/time formatting
53+
* <li><b>Naming Strategy:</b> snake_case for API consistency
54+
* </ul>
55+
*
56+
* <p><b>Compliance:</b>
57+
*
58+
* <ul>
59+
* <li>OWASP Top 10 2021 - A8 (Software and Data Integrity Failures)
60+
* <li>CWE-502 (Deserialization of Untrusted Data)
61+
* <li>OWASP API Security Top 10 - API8 (Injection)
62+
* </ul>
63+
*
64+
* @see <a
65+
* href="https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data">OWASP
66+
* Deserialization</a>
67+
* @see <a
68+
* href="https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization">Jackson
69+
* Polymorphic Deserialization</a>
70+
*/
71+
@Slf4j
72+
@Configuration
73+
@InfraGenerated
74+
public class JacksonConfiguration {
75+
76+
private static final String APPLICATION_BASE_PACKAGE = "com.example";
77+
78+
/**
79+
* Creates the primary ObjectMapper bean with security hardening and feature configuration.
80+
*
81+
* <p>This ObjectMapper is marked as {@code @Primary} and will be used throughout the application
82+
* for all JSON serialization/deserialization operations, including Spring MVC's HTTP message
83+
* conversion.
84+
*
85+
* <p><b>Security Considerations:</b>
86+
*
87+
* <ul>
88+
* <li>Default typing is DISABLED - prevents polymorphic deserialization attacks
89+
* <li>Polymorphic type validator restricts deserialization to application packages only
90+
* <li>Strict number parsing prevents overflow attacks
91+
* <li>Duplicate key detection prevents ambiguous JSON
92+
* </ul>
93+
*
94+
* @return fully configured and secured ObjectMapper instance
95+
*/
96+
@Bean
97+
@Primary
98+
public ObjectMapper objectMapper() {
99+
ObjectMapper mapper = new ObjectMapper();
100+
configureSecurityFeatures(mapper);
101+
registerModules(mapper);
102+
configureSerializationFeatures(mapper);
103+
configureDeserializationFeatures(mapper);
104+
configureParserFeatures(mapper);
105+
configurePropertyHandling(mapper);
106+
107+
log.info("ObjectMapper configured with security hardening and application-wide settings");
108+
return mapper;
109+
}
110+
111+
/**
112+
* Configures security features to prevent deserialization attacks.
113+
*
114+
* <p><b>Critical Security Settings:</b>
115+
*
116+
* <ul>
117+
* <li>Polymorphic type validation with application package allowlist
118+
* <li>No default typing enabled (would be a critical vulnerability)
119+
* <li>Only application classes can be polymorphically deserialized
120+
* </ul>
121+
*/
122+
private void configureSecurityFeatures(ObjectMapper om) {
123+
PolymorphicTypeValidator typeValidator =
124+
BasicPolymorphicTypeValidator.builder()
125+
.allowIfBaseType(APPLICATION_BASE_PACKAGE)
126+
.allowIfSubType(APPLICATION_BASE_PACKAGE)
127+
.build();
128+
129+
om.setPolymorphicTypeValidator(typeValidator);
130+
131+
// IMPORTANT: Do NOT call om.enableDefaultTyping() or activateDefaultTyping()
132+
// This would enable polymorphic deserialization globally and create RCE vulnerabilities
133+
134+
log.debug(
135+
"Security: Polymorphic type validation restricted to package: {}",
136+
forJava(APPLICATION_BASE_PACKAGE));
137+
}
138+
139+
/**
140+
* Registers Jackson modules for enhanced type support.
141+
*
142+
* <p>Modules provide serialization/deserialization support for:
143+
*
144+
* <ul>
145+
* <li>Java 8 date/time types (LocalDateTime, Instant, ZonedDateTime, etc.)
146+
* <li>Java 8 Optional types (Optional&lt;T&gt;)
147+
* <li>Parameter name preservation for constructor/method parameters
148+
* </ul>
149+
*/
150+
private void registerModules(ObjectMapper mapper) {
151+
mapper.registerModule(new JavaTimeModule());
152+
mapper.registerModule(new Jdk8Module());
153+
mapper.registerModule(new ParameterNamesModule());
154+
155+
log.debug("Registered Jackson modules: JavaTime, JDK8, ParameterNames");
156+
}
157+
158+
/**
159+
* Configures serialization behavior for JSON output.
160+
*
161+
* <p><b>Serialization Settings:</b>
162+
*
163+
* <ul>
164+
* <li>Dates as ISO-8601 strings (not timestamps) for human readability
165+
* <li>Empty collections/arrays written (not omitted) for API clarity
166+
* <li>Pretty printing disabled for production (reduces payload size)
167+
* </ul>
168+
*/
169+
private void configureSerializationFeatures(ObjectMapper om) {
170+
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
171+
om.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
172+
om.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
173+
om.disable(SerializationFeature.INDENT_OUTPUT); // Disable pretty-print for production
174+
175+
log.debug("Serialization: Dates as ISO-8601, no timestamps, compact output");
176+
}
177+
178+
/**
179+
* Configures deserialization behavior for JSON input.
180+
*
181+
* <p><b>Deserialization Settings:</b>
182+
*
183+
* <ul>
184+
* <li>Unknown properties ignored for backward compatibility
185+
* <li>Empty strings accepted as null for flexible input handling
186+
* <li>Fail on null for primitives (prevents unexpected NullPointerExceptions)
187+
* <li>Read date timestamps as milliseconds
188+
* <li>Accept single values as arrays for API flexibility
189+
* </ul>
190+
*/
191+
private void configureDeserializationFeatures(ObjectMapper mapper) {
192+
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
193+
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
194+
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
195+
mapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
196+
mapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
197+
mapper.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
198+
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
199+
mapper.enable(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS);
200+
201+
log.debug("Deserialization: Flexible input, strict primitives, overflow protection");
202+
}
203+
204+
/**
205+
* Configures JSON parser features for input validation.
206+
*
207+
* <p><b>Parser Settings:</b>
208+
*
209+
* <ul>
210+
* <li>Comments allowed in JSON for development/debugging
211+
* <li>Unquoted field names allowed for relaxed parsing
212+
* <li>Single quotes allowed as alternative to double quotes
213+
* <li>Duplicate keys rejected to prevent ambiguous input
214+
* <li>Trailing commas allowed for cleaner JSON editing
215+
* </ul>
216+
*/
217+
private void configureParserFeatures(ObjectMapper om) {
218+
om.enable(JsonParser.Feature.ALLOW_COMMENTS);
219+
om.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
220+
om.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
221+
om.enable(JsonReadFeature.ALLOW_TRAILING_COMMA.mappedFeature());
222+
om.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
223+
224+
log.debug("Parser: Flexible input formats, strict duplicate detection");
225+
}
226+
227+
/**
228+
* Configures property naming and null handling strategies.
229+
*
230+
* <p><b>Property Settings:</b>
231+
*
232+
* <ul>
233+
* <li>snake_case naming for API consistency (camelCase → snake_case)
234+
* <li>Null values excluded from JSON output (reduces payload size)
235+
* <li>Empty collections included for API clarity
236+
* </ul>
237+
*/
238+
private void configurePropertyHandling(ObjectMapper om) {
239+
om.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
240+
om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
241+
om.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
242+
243+
log.debug("Properties: snake_case naming, null values excluded");
244+
}
245+
246+
/**
247+
* Bean for registering polymorphic subtypes automatically.
248+
*
249+
* <p>This bean is called by Spring after the ObjectMapper is created, allowing custom subtype
250+
* registration logic to be added by other configuration classes.
251+
*
252+
* @param objectMapper the primary ObjectMapper
253+
* @return list of polymorphic type registrars
254+
*/
255+
@Bean
256+
public List<PolymorphicTypeRegistrar> polymorphicTypeRegistrars(ObjectMapper objectMapper) {
257+
// Other configuration classes can implement PolymorphicTypeRegistrar
258+
// to automatically register their polymorphic types
259+
return List.of();
260+
}
261+
262+
/**
263+
* Interface for components that need to register polymorphic types with Jackson.
264+
*
265+
* <p>Implement this interface in configuration classes that need to register polymorphic subtypes
266+
* for secure deserialization.
267+
*
268+
* <p>Example:
269+
*
270+
* <pre>{@code
271+
* @Component
272+
* public class EventTypeRegistrar implements PolymorphicTypeRegistrar {
273+
* public void registerTypes(ObjectMapper mapper) {
274+
* // Auto-discover and register event types
275+
* }
276+
* }
277+
* }</pre>
278+
*/
279+
public interface PolymorphicTypeRegistrar {
280+
/**
281+
* Registers polymorphic subtypes with the given ObjectMapper.
282+
*
283+
* @param mapper the ObjectMapper to register types with
284+
*/
285+
void registerTypes(ObjectMapper mapper);
286+
}
287+
}

src/main/java/com/example/arInfra/config/ObjectMapperConfig.java

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)