Lab 05: Security in Node.js

Time: 30 minutes | Level: Advanced | Docker: docker run -it --rm node:20-alpine sh

Overview

OWASP Top 10 in Node.js context: prototype pollution, ReDoS, path traversal, deserialization attacks, CSP headers, rate limiting, and input validation patterns.


Step 1: Prototype Pollution

// VULNERABLE: Recursive merge without sanitization
function deepMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (source[key] && typeof source[key] === 'object') {
      target[key] = target[key] ?? {};
      deepMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

// Attack: pollute Object.prototype
const attack = JSON.parse('{"__proto__": {"isAdmin": true}}');
deepMerge({}, attack);
console.log({}.isAdmin); // true! — All objects now have isAdmin

// SECURE: Check for dangerous keys
function safeMerge(target, source, depth = 0) {
  if (depth > 10) throw new Error('Max depth exceeded'); // Prevent deep recursion
  for (const key of Object.keys(source)) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
      continue; // Skip dangerous keys
    }
    if (source[key] && typeof source[key] === 'object') {
      target[key] = target[key] ?? Object.create(null);
      safeMerge(target[key], source[key], depth + 1);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

// Use Object.create(null) for safe lookup tables
const safe = Object.create(null);
safe['__proto__'] = 'not a prototype!';
console.log(safe['__proto__']); // 'not a prototype!' (just a key)

// Reset pollution from demo (don't do this in production!)
delete Object.prototype.isAdmin;

Step 2: ReDoS (Regular Expression Denial of Service)


Step 3: Path Traversal


Step 4: Deserialization & Injection


Step 5: Security Headers


Step 6: Input Validation


Step 7: Secure Coding Demo

Run verification:

📸 Verified Output:


Summary

Vulnerability
Attack
Defense

Prototype pollution

{"__proto__": {...}}

Check keys, use Object.create(null)

ReDoS

Complex regex + long input

Timeout, rewrite patterns

Path traversal

../../etc/passwd

path.resolve + starts-with check

SQL injection

'; DROP TABLE

Parameterized queries

XSS

<script> in input

Escape HTML, CSP headers

CSRF

Forged requests

CSRF tokens, SameSite cookies

Deserialize

eval(untrustedData)

JSON.parse + validation only

DoS

Unbounded resources

Rate limiting, input size limits

Last updated