JSON Security: Preventing Injection Attacks

Protect your applications from JSON-based vulnerabilities and attacks

Last updated: January 21, 2026 • 10 min read

Common JSON Security Vulnerabilities

1. JSON Injection Attacks

JSON injection occurs when untrusted data is inserted into JSON without proper validation or escaping.

Vulnerable Code Example:

// ❌ DANGEROUS - Never do this!
const userInput = req.query.name; // User controls this
const json = `{"name": "${userInput}", "role": "user"}`;
const data = JSON.parse(json);

Attack Example:

// Malicious input:
name = 'John", "role": "admin", "x": "'

// Results in:
{"name": "John", "role": "admin", "x": "", "role": "user"}
// User just became an admin!

✅ Secure Solution:

// Use proper JSON serialization
const userInput = req.query.name;
const data = {
  name: userInput,  // JSON.stringify handles escaping
  role: "user"
};
const json = JSON.stringify(data);

2. Cross-Site Scripting (XSS) via JSON

When JSON data is rendered in HTML without sanitization, it can lead to XSS attacks.

Vulnerable Code:

// ❌ DANGEROUS
<script>
  const userData = ${JSON.stringify(userInput)};
  document.getElementById('name').innerHTML = userData.name;
</script>

Attack Example:

// Malicious input:
name: "<img src=x onerror='alert(document.cookie)'>"

// Gets executed in the browser!

✅ Secure Solution:

// Use textContent instead of innerHTML
const userData = JSON.parse(safeJsonString);
document.getElementById('name').textContent = userData.name;

// Or sanitize HTML
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userData.name);

3. Mass Assignment Vulnerabilities

Accepting arbitrary JSON properties can allow attackers to modify fields they shouldn't have access to.

Vulnerable Code:

// ❌ DANGEROUS - Accepts any property
app.post('/api/users/:id', (req, res) => {
  const updates = req.body;  // Could contain anything!
  await User.update(req.params.id, updates);
});

Attack Example:

// Malicious request:
POST /api/users/123
{
  "name": "John",
  "role": "admin",        // ❌ User shouldn't be able to set this
  "isVerified": true,     // ❌ Or this
  "credits": 999999       // ❌ Or this
}

✅ Secure Solution:

// Whitelist allowed fields
app.post('/api/users/:id', (req, res) => {
  const allowedFields = ['name', 'email', 'bio'];
  const updates = {};
  
  for (const field of allowedFields) {
    if (req.body[field] !== undefined) {
      updates[field] = req.body[field];
    }
  }
  
  await User.update(req.params.id, updates);
});

4. Prototype Pollution

JSON parsing can be exploited to modify JavaScript object prototypes.

Vulnerable Code:

// ❌ DANGEROUS - Recursive merge without protection
function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object') {
      target[key] = merge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

const userInput = JSON.parse(req.body);
merge(config, userInput);  // Vulnerable!

Attack Example:

// Malicious JSON:
{
  "__proto__": {
    "isAdmin": true
  }
}

// Now ALL objects have isAdmin: true!
console.log({}.isAdmin);  // true - Oh no!

✅ Secure Solution:

// Use Object.assign or spread operator with validation
function secureMerge(target, source) {
  const safeSource = {};
  for (const key in source) {
    // Skip prototype pollution attempts
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
      continue;
    }
    if (source.hasOwnProperty(key)) {
      safeSource[key] = source[key];
    }
  }
  return { ...target, ...safeSource };
}

5. Denial of Service (DoS) Attacks

Large Payload Attack:

// Attacker sends extremely large JSON
// 100MB+ of data to crash server

✅ Protection:

// Express.js - Limit payload size
app.use(express.json({ 
  limit: '1mb'  // Maximum 1MB
}));

// Or custom middleware
app.use((req, res, next) => {
  const contentLength = req.headers['content-length'];
  if (contentLength > 1048576) {  // 1MB
    return res.status(413).json({ 
      error: 'Payload too large' 
    });
  }
  next();
});

JSON Security Best Practices

1. Always Validate Input

// Use JSON Schema for validation
const Ajv = require('ajv');
const ajv = new Ajv();

const schema = {
  type: 'object',
  properties: {
    name: { type: 'string', maxLength: 100 },
    age: { type: 'number', minimum: 0, maximum: 150 },
    email: { type: 'string', format: 'email' }
  },
  required: ['name', 'email'],
  additionalProperties: false  // Reject unknown fields
};

const validate = ajv.compile(schema);
if (!validate(data)) {
  throw new Error('Invalid data: ' + JSON.stringify(validate.errors));
}

2. Use Content-Type Headers Correctly

// Server-side
res.setHeader('Content-Type', 'application/json; charset=utf-8');

// Client-side validation
if (response.headers.get('Content-Type') !== 'application/json') {
  throw new Error('Expected JSON response');
}

3. Implement Rate Limiting

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,  // Limit each IP to 100 requests per window
  message: 'Too many requests, please try again later'
});

app.use('/api/', apiLimiter);

4. Sanitize Before Output

// For HTML context
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(jsonData.userInput);

// For JavaScript context
const escaped = JSON.stringify(jsonData)
  .replace(/</g, '\u003c')
  .replace(/>/g, '\u003e');

5. Use HTTPS Always

// Redirect HTTP to HTTPS
app.use((req, res, next) => {
  if (!req.secure && process.env.NODE_ENV === 'production') {
    return res.redirect('https://' + req.headers.host + req.url);
  }
  next();
});

6. Implement CORS Properly

const cors = require('cors');

// Don't use wildcard in production!
app.use(cors({
  origin: ['https://yourdomain.com'],  // Whitelist specific domains
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

7. Log Security Events

// Log suspicious activity
app.use((req, res, next) => {
  try {
    const data = JSON.parse(req.body);
    
    // Check for suspicious patterns
    if (JSON.stringify(data).includes('__proto__')) {
      logger.warn('Prototype pollution attempt detected', {
        ip: req.ip,
        body: req.body
      });
      return res.status(400).json({ error: 'Invalid request' });
    }
    
    next();
  } catch (error) {
    logger.error('JSON parsing error', { error: error.message });
    res.status(400).json({ error: 'Invalid JSON' });
  }
});

Security Checklist

✅ JSON Security Checklist

  • ✅ Never construct JSON strings manually with user input
  • ✅ Always use JSON.stringify() and JSON.parse()
  • ✅ Validate all JSON input with schemas
  • ✅ Whitelist allowed fields (no mass assignment)
  • ✅ Sanitize output before rendering in HTML
  • ✅ Limit payload sizes to prevent DoS
  • ✅ Check for prototype pollution attempts
  • ✅ Use HTTPS for all JSON API calls
  • ✅ Implement proper CORS policies
  • ✅ Rate limit API endpoints
  • ✅ Log security-related events
  • ✅ Keep dependencies updated

Tools for Secure JSON Handling

🔍 JSON Validator

Validate JSON structure before processing

Validate JSON →

📋 JSON Schema Validator

Validate against JSON Schema for security

Validate Schema →

Conclusion

JSON security is critical for protecting your applications and users. By following these best practices and staying vigilant against common vulnerabilities, you can significantly reduce your attack surface. Remember: never trust user input, always validate, and sanitize before output.

Secure Your JSON Data

Start by validating your JSON to ensure it's well-formed and safe!

Validate JSON Free →