jackson-deserialization-validation · git:20260424.d50cf5a · 2026-04-24 · sha256 7e876302e850d92a

jackson-deserialization-validation git:20260424.d50cf5aA

Immutable. This exact content never changes and is served at /api/v1/blob/7e876302e850d92a.

---
name: jackson-deserialization-validation
description: Securing Jackson JSON deserialization by validating input before processing and preventing unknown properties.
---

# Jackson Deserialization Validation

## Vulnerability Pattern

When Jackson deserializes JSON with unknown properties, if not handled properly, malicious extra properties can bypass validation logic:

```json
{
  "validProperty": "legitimate value",
  "": {
    "enabled": true,
    "other": "malicious"
  }
}
```

The empty key `""` can be overlooked by simple property checks.

## Jackson Configuration

### 1. Fail on Unknown Properties
```java
@JsonIgnoreProperties(ignoreUnknown = false)
public class MyFilter {
    @JsonProperty
    private String function;

    // This will throw exception on unknown properties
}
```

### 2. Custom Deserialization
```java
@JsonDeserialize(using = CustomDeserializer.class)
public class MyFilter {
    // Custom logic during deserialization
}
```

### 3. @JsonAnySetter for Validation
```java
public class MyFilter {
    @JsonProperty
    private String function;

    @JsonAnySetter
    public void handleUnknown(String key, Object value) {
        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("Empty property keys not allowed");
        }
        // Validate other unknown properties
    }
}
```

## Validation Strategies

### Strategy 1: Whitelist Allowed Keys
```java
private static final Set<String> ALLOWED_KEYS =
    Set.of("function", "type", "name");

@JsonAnySetter
public void handleProperty(String key, Object value) {
    if (!ALLOWED_KEYS.contains(key)) {
        throw new IllegalArgumentException(
            "Unknown property: " + key);
    }
}
```

### Strategy 2: Reject Empty/Null Keys
```java
@JsonAnySetter
public void validateKey(String key, Object value) {
    if (key == null || key.trim().isEmpty()) {
        throw new IllegalArgumentException(
            "Property keys cannot be empty or null");
    }
}
```

### Strategy 3: Post-Deserialization Validation
```java
@JsonProperty
private Map<String, Object> properties;

@PostConstruct
public void validate() {
    for (String key : properties.keySet()) {
        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException(
                "Empty keys not allowed");
        }
    }
}
```

## Implementation in Druid

### Finding Vulnerable Classes
1. Search for `@JsonTypeName("javascript")`
2. Look for JavaScript filter/transform specs
3. Check deserialization of filter objects

### Key Locations
- `JavaScriptFilter.java` - Main JavaScript filter class
- Filter spec handlers in indexing-service
- Transform spec deserialization

### Fix Pattern
```java
@JsonIgnoreProperties(ignoreUnknown = false)
public class JavaScriptFilter {
    @JsonProperty
    private String function;

    @JsonAnySetter
    public void handleUnknown(String key, Object value) {
        throw new IllegalArgumentException(
            "Unknown property '" + key +
            "' in JavaScript filter specification");
    }
}
```

## Testing the Fix

### Test Case 1: Valid Request
```java
String json = "{\"type\":\"javascript\",\"function\":\"function(){return true;}\"}";
JavaScriptFilter filter = mapper.readValue(json, JavaScriptFilter.class);
// Should succeed
```

### Test Case 2: Empty Key (Should Fail)
```java
String json = "{\"type\":\"javascript\",\"function\":\"...\",\"\":{\"enabled\":true}}";
assertThrows(JsonMappingException.class, () ->
    mapper.readValue(json, JavaScriptFilter.class));
// Should throw exception
```

### Test Case 3: Null Key (Should Fail)
```java
String json = "{\"type\":\"javascript\",null:{\"value\":\"bad\"}}";
assertThrows(JsonMappingException.class, () ->
    mapper.readValue(json, JavaScriptFilter.class));
```

## Druid-Specific Considerations

1. **Custom ObjectMapper**: Druid may use custom ObjectMapper configuration
2. **Multiple Filter Types**: Fix may need to apply to multiple filter classes
3. **Backward Compatibility**: Ensure legitimate requests still work
4. **Nested Specs**: Check if transformSpec or other nested objects also need fixes

## Recommended Approach

1. Use `@JsonIgnoreProperties(ignoreUnknown = false)` to detect unknown properties
2. Add `@JsonAnySetter` to validate property names
3. Reject any empty string keys explicitly
4. Test both via HTTP API and direct deserialization