Skip to content

About

Master Web Security: OWASP Top 10, CORS, CSP, CSRF, XSS defense, rate limiting, and cryptographic foundations

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Learn Web Application Security: From Zero to Staff AppSec Architect

Test Suite OWASP Top 10 License: MIT

A masterclass on modern Web Application Security (AppSec), Same-Origin Policy (SOP), Content Security Policy (CSP v3), XSS/CSRF defense-in-depth, Argon2id cryptography, JWT hardening, SSRF prevention, and enterprise threat modeling.


Table of Contents

  1. Stage 1: Absolute Beginner Foundations & The Browser Security Model
  2. Stage 2: OWASP Top 10 Vulnerabilities & Exploitation Vectors
  3. Stage 3: Cross-Site Scripting (XSS) & Content Security Policy (CSP v3)
  4. Stage 4: CSRF Defense, Clickjacking & Cookie Prefix Architecture
  5. Stage 5: Cryptography, Argon2id & JWT Threat Mitigation
  6. Stage 6: SSRF Prevention, Network Segmentation & Hardened Headers
  7. Stage 7: Staff Security Architect Interview Handbook & Production Cheatsheet

Architectural Comparison: Defense-in-Depth vs. Perimeter-Only Security

graph TD
    subgraph "Legacy Perimeter Security"
        WAF["Web Application Firewall (WAF)"] -->|"Single Barrier"| APP["Monolithic Web App"]
        APP -->|"Plaintext Secrets & Unchecked Queries"| DB[("Database")]
    end

    subgraph "Modern Zero-Trust Defense-in-Depth"
        CLIENT["Client Browser (CSP v3 + Trusted Types)"] --> WAF2["Edge WAF & DDoS Shield"]
        WAF2 --> APP2["Hardened Application (Zod Validation + Rate Limit)"]
        APP2 --> M2M["mTLS & Scoped RBAC Tokens"]
        M2M --> DB2[("Encrypted DB with Parameterized Queries")]
    end
Loading

1. Stage 1: Absolute Beginner Foundations & The Browser Security Model

1.1 The Same-Origin Policy (SOP)

The foundational security pillar of modern web browsers: scripts running on one origin cannot read or mutate data from another origin unless explicitly granted permission via Cross-Origin Resource Sharing (CORS).

An origin is defined strictly as the triplet: Scheme + Host + Port.

graph TD
    BASE["Origin: https://app.example.com:443"]
    BASE -->|"Same Scheme, Same Host, Same Port"| MATCH["https://app.example.com/api/profile (SAME ORIGIN)"]
    BASE -->|"Different Subdomain"| FAIL1["https://api.example.com (CROSS ORIGIN)"]
    BASE -->|"Different Scheme"| FAIL2["http://app.example.com (CROSS ORIGIN)"]
    BASE -->|"Different Port"| FAIL3["https://app.example.com:8443 (CROSS ORIGIN)"]
Loading

2. Stage 2: OWASP Top 10 Vulnerabilities & Exploitation Vectors

2.1 The Core Vulnerability Taxonomy

  1. A01: Broken Access Control (BOLA / IDOR): Flaws allowing users to access resources belonging to other users simply by changing an ID parameter (GET /api/documents/doc-9921).
  2. A02: Cryptographic Failures: Using deprecated hashing algorithms (MD5, SHA-1), storing plaintext credentials, or weak TLS configurations.
  3. A03: Injection (SQL, Command, LDAP): Concatenating untrusted user input directly into interpreter execution strings.
  4. A04: Insecure Design: Flaws in business logic (e.g. lack of rate limiting on password reset flows, permitting brute-force enumeration).
  5. A05: Security Misconfiguration: Unnecessary features enabled, default accounts/passwords, overly permissive CORS (Access-Control-Allow-Origin: * with credentials), verbose stack traces in production.
  6. A10: Server-Side Request Forgery (SSRF): Flaws allowing attackers to induce the backend server to make unauthorized HTTP requests to internal VPC resources (e.g., querying AWS IMDS http://169.254.169.254/latest/meta-data/).

3. Stage 3: Cross-Site Scripting (XSS) & Content Security Policy (CSP v3)

3.1 The Three Variants of XSS

  • Stored (Persistent) XSS: Malicious script is saved in the database (e.g., in a comment or profile bio) and served to subsequent visitors.
  • Reflected XSS: Malicious script is included in a URL parameter or form input and immediately echoed back in the response page.
  • DOM-Based XSS: Malicious script executes entirely on the client side via unsafe DOM manipulation (innerHTML, document.write, eval()).
graph LR
    ATTACKER["Attacker"] -->|"Injects <script>alert(1)</script>"| SERVER[("Database / Web Server")]
    SERVER -->|"Serves Unsanitized Markup"| VICTIM["Victim Browser"]
    VICTIM -->|"Script Executes in Victim Context"| EXFIL["Exfiltrates Session Cookies / Tokens"]
Loading

3.2 Hardened CSP v3 Nonce-Based Policy

A strict Content Security Policy eliminates 99% of XSS vectors by disallowing inline scripts unless they carry a cryptographically secure, per-request random nonce:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-rAnd0m12345' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; require-trusted-types-for 'script';

4. Stage 4: CSRF Defense, Clickjacking & Cookie Prefix Architecture

4.1 Cross-Site Request Forgery (CSRF) & SameSite Cookies

CSRF tricks an authenticated user's browser into executing an unwanted action on a trusted application:

graph TD
    USER["User logged into bank.com (Session Cookie in Browser)"]
    EVIL["User visits malicious-site.com"]
    EVIL -->|"Sends hidden auto-submitting form: POST bank.com/transfer"| BANK["bank.com Server"]
    BANK -->|"Browser automatically attaches bank.com cookie!"| LOSS["Unauthorized Transfer Executed!"]
Loading

Defense: SameSite=Strict and Synchronizer Token Pattern.

4.2 Secure Cookie Prefix Architecture (__Host- & __Secure-)

Hardened cookie prefixes enforced by modern browsers:

  • __Secure-: Cookie must be sent over HTTPS (Secure flag required).
  • __Host-: Cookie must be sent over HTTPS, must have path=/, and cannot have a Domain attribute set (prevents subdomain shadowing attacks).
Set-Cookie: __Host-Session=s%3A99d8a1.f9d8; Path=/; Secure; HttpOnly; SameSite=Strict; Max-Age=86400

5. Stage 5: Cryptography, Argon2id & JWT Threat Mitigation

5.1 Modern Password Hashing: Argon2id vs. bcrypt vs. PBKDF2

Argon2id (winner of the Password Hashing Competition) provides memory-hard and time-hard resistance against GPU and ASIC cracking:

// src/services/crypto.service.ts
import argon2 from 'argon2';

export async function hashPassword(plainTextPassword: string): Promise<string> {
  return await argon2.hash(plainTextPassword, {
    type: argon2.argon2id,
    memoryCost: 65536, // 64 MiB RAM requirement
    timeCost: 3,       // 3 iterations
    parallelism: 4,    // 4 parallel threads
  });
}

export async function verifyPassword(storedHash: string, candidatePassword: string): Promise<boolean> {
  return await argon2.verify(storedHash, candidatePassword);
}

5.2 JWT Hardening: Mitigating the alg: none and Key Confusion Attacks

  • Reject alg: none: Always explicitly specify allowed algorithms (algorithms: ['RS256']).
  • Asymmetric Signing (RS256/ES256): Sign tokens with a private RSA key; verify tokens with a public key. This prevents services that only need verification from minting forged tokens.
  • Short Lifetime + Refresh Token Rotation: Access token expires in 15 minutes; Refresh token stored in __Host- cookie with database revocation list.

6. Stage 6: SSRF Prevention, Network Segmentation & Hardened Headers

6.1 Server-Side Request Forgery (SSRF) Defense Architecture

When fetching user-provided URLs (e.g. webhooks, link previews), attackers can target internal cloud metadata endpoints (http://169.254.169.254) or internal IP ranges (10.0.0.0/8, 127.0.0.1, 192.168.0.0/16).

// src/services/safeFetch.service.ts
import dns from 'dns/promises';
import net from 'net';

const PRIVATE_IP_RANGES = [
  /^127\./,                    // Loopback
  /^10\./,                     // Class A private
  /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // Class B private
  /^192\.168\./,               // Class C private
  /^169\.254\./,               // AWS Link-local / Cloud Metadata
  /^::1$/,                     // IPv6 loopback
  /^fc00:/,                    // IPv6 private
];

export async function validateAndFetchUrl(targetUrl: string) {
  const parsed = new URL(targetUrl);

  // 1. Enforce HTTPS only
  if (parsed.protocol !== 'https:') {
    throw new Error('SSRF Guard: Only HTTPS requests are permitted.');
  }

  // 2. Resolve DNS IP addresses before fetching (Prevents DNS Rebinding)
  const addresses = await dns.resolve4(parsed.hostname);
  for (const ip of addresses) {
    if (PRIVATE_IP_RANGES.some((regex) => regex.test(ip))) {
      throw new Error(`SSRF Guard: Target IP "${ip}" belongs to a protected internal network range.`);
    }
  }

  // 3. Safe fetch with timeout and redirection limit
  return await fetch(targetUrl, {
    redirect: 'error', // Disallow redirects to prevent DNS rebinding hops
    signal: AbortSignal.timeout(5000),
  });
}


Comprehensive Production AppSec Defense & Middleware Architecture

1. Dynamic Per-Request CSP Nonce & Helmet Security Headers Middleware

Generates a unique cryptographically random nonce for every HTTP request, allowing inline scripts safely without 'unsafe-inline':

// src/middleware/securityHeaders.ts
import { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import crypto from 'crypto';

export function securityHeaders() {
  return (req: Request, res: Response, next: NextFunction) => {
    // 1. Generate 128-bit cryptographically secure random nonce
    const nonce = crypto.randomBytes(16).toString('base64');
    res.locals.cspNonce = nonce;

    // 2. Configure Helmet with strict Content-Security-Policy v3
    helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          scriptSrc: [
            "'self'",
            `'nonce-${nonce}'`,
            "'strict-dynamic'",
            // Fallback for older browsers
            'https:',
          ],
          styleSrc: ["'self'", `'nonce-${nonce}'`],
          imgSrc: ["'self'", 'data:', 'https://images.unsplash.com'],
          objectSrc: ["'none'"],
          baseUri: ["'none'"],
          formAction: ["'self'"],
          frameAncestors: ["'none'"], // Complete Clickjacking prevention
          upgradeInsecureRequests: [],
        },
      },
      crossOriginEmbedderPolicy: true,
      crossOriginOpenerPolicy: { policy: 'same-origin' },
      crossOriginResourcePolicy: { policy: 'same-origin' },
      dnsPrefetchControl: { allow: false },
      frameguard: { action: 'deny' },
      hidePoweredBy: true, // Strips X-Powered-By: Express
      hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true,
      },
      ieNoOpen: true,
      noSniff: true,
      referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
      xssFilter: true,
    })(req, res, next);
  };
}

export function generateAuthenticationOptions(rpId: string): { challenge: string; rpId: string; timeout: number } {
  return {
    challenge: crypto.randomBytes(32).toString('base64url'),
    rpId,
    timeout: 60000,
  };
}

export function verifyPasskeySignature(clientDataJSON: string, authenticatorData: Buffer, signature: Buffer, publicKeyPem: string): boolean {
  const clientDataHash = crypto.createHash('sha256').update(clientDataJSON).digest();
  const signatureBase = Buffer.concat([authenticatorData, clientDataHash]);

  const verifier = crypto.createVerify('SHA256');
  verifier.update(signatureBase);
  return verifier.verify(publicKeyPem, signature);
}

2. Double-Submit Cookie & Signed CSRF Token Middleware

Validates that incoming mutation requests contain a matching CSRF token signed with an HMAC secret:

// src/middleware/csrfProtection.ts
import { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';

const CSRF_SECRET = process.env.CSRF_SECRET || 'super-secret-csrf-signing-key-32b';

export function generateCsrfToken(sessionId: string): string {
  const timestamp = Date.now().toString();
  const salt = crypto.randomBytes(8).toString('hex');
  const signature = crypto
    .createHmac('sha256', CSRF_SECRET)
    .update(`${sessionId}.${timestamp}.${salt}`)
    .digest('hex');

  return `${timestamp}.${salt}.${signature}`;
}

export function verifyCsrfToken(sessionId: string, token: string, maxAgeMs: number = 3600000): boolean {
  const parts = token.split('.');
  if (parts.length !== 3) return false;

  const [timestampStr, salt, providedSig] = parts;
  const timestamp = parseInt(timestampStr, 10);

  // Check token age (1 hour limit)
  if (Date.now() - timestamp > maxAgeMs) {
    return false;
  }

  // Verify HMAC signature using constant-time comparison
  const expectedSig = crypto
    .createHmac('sha256', CSRF_SECRET)
    .update(`${sessionId}.${timestampStr}.${salt}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(providedSig), Buffer.from(expectedSig));
}

export function csrfMiddleware() {
  return (req: Request, res: Response, next: NextFunction) => {
    // Safe HTTP methods do not require CSRF tokens
    if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
      return next();
    }

    const sessionId = req.cookies?.['__Host-SessionId'];
    const submittedToken = (req.headers['x-csrf-token'] as string) || req.body?._csrf;

    if (!sessionId || !submittedToken || !verifyCsrfToken(sessionId, submittedToken)) {
      return res.status(403).json({
        type: 'https://api.example.com/errors/csrf-validation-failed',
        title: 'Forbidden',
        status: 403,
        detail: 'Invalid or missing CSRF authenticity token.',
        instance: req.originalUrl,
      });
    }

    next();
  };
}

export function generateAuthenticationOptions(rpId: string): { challenge: string; rpId: string; timeout: number } {
  return {
    challenge: crypto.randomBytes(32).toString('base64url'),
    rpId,
    timeout: 60000,
  };
}

export function verifyPasskeySignature(clientDataJSON: string, authenticatorData: Buffer, signature: Buffer, publicKeyPem: string): boolean {
  const clientDataHash = crypto.createHash('sha256').update(clientDataJSON).digest();
  const signatureBase = Buffer.concat([authenticatorData, clientDataHash]);

  const verifier = crypto.createVerify('SHA256');
  verifier.update(signatureBase);
  return verifier.verify(publicKeyPem, signature);
}

3. Production JWT Access & Refresh Token Rotation with Redis Blacklist

Mitigates token theft through short-lived access tokens and single-use refresh token rotation:

// src/services/auth.service.ts
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL!);

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET!;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;

export interface UserSessionPayload {
  userId: string;
  role: string;
  tokenFamilyId: string; // Unique ID to track family of rotated tokens
}

export async function issueTokens(userId: string, role: string, existingFamilyId?: string) {
  const tokenFamilyId = existingFamilyId || crypto.randomUUID();

  // 1. Short-lived Access Token (15 minutes)
  const accessToken = jwt.sign(
    { userId, role, tokenFamilyId },
    ACCESS_SECRET,
    { expiresIn: '15m', algorithm: 'HS256' }
  );

  // 2. Single-use Refresh Token (7 days)
  const refreshToken = jwt.sign(
    { userId, role, tokenFamilyId, jti: crypto.randomUUID() },
    REFRESH_SECRET,
    { expiresIn: '7d', algorithm: 'HS256' }
  );

  // Store active family in Redis (valid for 7 days)
  await redis.set(`auth:family:${tokenFamilyId}`, userId, 'EX', 7 * 86400);

  return { accessToken, refreshToken, tokenFamilyId };
}

export async function rotateRefreshToken(oldRefreshToken: string) {
  try {
    const payload = jwt.verify(oldRefreshToken, REFRESH_SECRET) as any;
    const { userId, role, tokenFamilyId, jti } = payload;

    // Detect Replay Attack: Has this refresh token JTI been used before?
    const isBlacklisted = await redis.get(`auth:blacklist:${jti}`);
    if (isBlacklisted) {
      // BREACH DETECTED: Someone attempted to reuse a spent token!
      // Invalidate the entire token family immediately!
      await redis.del(`auth:family:${tokenFamilyId}`);
      throw new Error('Token reuse detected. All sessions in this family have been revoked.');
    }

    // Blacklist the used refresh token immediately
    await redis.set(`auth:blacklist:${jti}`, 'used', 'EX', 7 * 86400);

    // Verify token family is still valid
    const activeUser = await redis.get(`auth:family:${tokenFamilyId}`);
    if (!activeUser) {
      throw new Error('Session has been revoked.');
    }

    // Issue brand new token pair within same family
    return await issueTokens(userId, role, tokenFamilyId);
  } catch (err: any) {
    throw new Error(`Authentication failure: ${err.message}`);
  }
}

4. Input Sanitization & HTML Entity Encoding Engine

Prevents XSS in rich-text CMS inputs using JSDOM and DOMPurify:

// src/utils/sanitizer.ts
import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window as any);

// Configure strict DOMPurify whitelist
export function sanitizeRichText(dirtyHtml: string): string {
  return DOMPurify.sanitize(dirtyHtml, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li', 'code', 'pre'],
    ALLOWED_ATTR: ['href', 'target', 'rel'],
    // Force rel="noopener noreferrer" on external links
    ADD_ATTR: ['target'],
    FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form'],
    FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
  });
}

// Plain text encoder for safe HTML interpolation
export function escapeHtml(unsafeString: string): string {
  return unsafeString
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

5. Account Lockout & Brute-Force Rate Limiting Controller

Slows down brute-force credential stuffing with progressive delays and temporary lockout:

// src/services/loginRateLimit.service.ts
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL!);

export async function registerFailedLoginAttempt(accountEmail: string): Promise<{ isLocked: boolean; remainingAttempts: number; lockoutSeconds?: number }> {
  const key = `failed_login:${accountEmail}`;
  const attempts = await redis.incr(key);

  if (attempts === 1) {
    await redis.expire(key, 900); // 15 minute observation window
  }

  if (attempts >= 5) {
    const lockoutDuration = 900; // 15 minute lockout
    await redis.set(`lockout:${accountEmail}`, 'locked', 'EX', lockoutDuration);
    return { isLocked: true, remainingAttempts: 0, lockoutSeconds: lockoutDuration };
  }

  return { isLocked: false, remainingAttempts: 5 - attempts };
}

export async function isAccountLocked(accountEmail: string): Promise<boolean> {
  const locked = await redis.get(`lockout:${accountEmail}`);
  return locked !== null;
}

export async function clearFailedLogins(accountEmail: string) {
  await redis.del(`failed_login:${accountEmail}`);
  await redis.del(`lockout:${accountEmail}`);
}

6. Sensitive Data Masking & PII Redaction Engine

Prevents accidental leakage of social security numbers, credit cards, and API secrets into centralized log aggregators:

// src/utils/piiMasker.ts
export function maskSensitiveData(payload: Record<string, any>): Record<string, any> {
  const SENSITIVE_KEY_PATTERNS = [
    /password/i,
    /secret/i,
    /token/i,
    /apiKey/i,
    /auth/i,
    /creditCard/i,
    /cvv/i,
    /ssn/i,
  ];

  const CARD_REGEX = /�(?:\d[ -]*?){13,16}�/g;
  const EMAIL_REGEX = /([a-zA-Z0-9_\.-]+)@([a-zA-Z0-9_\.-]+\.[a-zA-Z]{2,6})/g;

  function redactValue(val: any): any {
    if (typeof val === 'string') {
      let redacted = val.replace(CARD_REGEX, '****-****-****-****');
      redacted = redacted.replace(EMAIL_REGEX, '$1***@$2');
      return redacted;
    }
    if (typeof val === 'object' && val !== null) {
      const cloned: Record<string, any> = Array.isArray(val) ? [] : {};
      for (const [k, v] of Object.entries(val)) {
        if (SENSITIVE_KEY_PATTERNS.some((rx) => rx.test(k))) {
          cloned[k] = '[REDACTED]';
        } else {
          cloned[k] = redactValue(v);
        }
      }
      return cloned;
    }
    return val;
  }

  return redactValue(payload);
}

7. Zero-Trust Mutual TLS (mTLS) Node.js Server & Client Configuration

Enforces bidirectional X.509 certificate authentication between microservices:

// src/server/mtlsServer.ts
import https from 'https';
import fs from 'fs';
import { app } from './app';

const mtlsOptions: https.ServerOptions = {
  // Server Identity
  key: fs.readFileSync('/etc/ssl/server.key'),
  cert: fs.readFileSync('/etc/ssl/server.crt'),

  // Trusted Internal Certificate Authority (CA)
  ca: fs.readFileSync('/etc/ssl/internal-ca.crt'),

  // Mutual TLS: Enforce client certificate validation
  requestCert: true,
  rejectUnauthorized: true, // Rejects connection immediately if client certificate is untrusted

  // Enforce TLS 1.3 only
  minVersion: 'TLSv1.3',
};

const server = https.createServer(mtlsOptions, app);

server.listen(8443, () => {
  console.log('Secure mTLS Microservice listening on port 8443');
});

8. SQL Injection Anatomy: Ast Manipulation vs. Parameterized Plan

-- VULNERABLE CODE: String Concatenation
-- Input: admin' OR '1'='1' --
SELECT * FROM users WHERE email = 'admin' OR '1'='1' --' AND password = '...';
-- Result: AST modified! The condition '1'='1' evaluates true for all rows; attacker logs in as admin without password!

-- DEFENSE: Prepared Statement / Parameterized Query
SELECT * FROM users WHERE email = $1 AND password = $2;
-- Database engine treats $1 strictly as a literal string value, never altering the parser syntax tree.

9. Automated Security Audit & Hardening Checklist

  1. Never store plaintext secrets in repositories: Use git hooks to scan for high-entropy tokens with tools like TruffleHog or Gitleaks.
  2. Rotate Database and API Credentials regularly: Use AWS Secrets Manager or HashiCorp Vault with automated 30-day rotation.
  3. Audit Third-Party NPM Dependencies in CI: Enforce npm audit --audit-level=high or Snyk scans on every pull request.
  4. Enforce Least-Privilege IAM Roles: Application servers should only have permission to execute specific database stored procedures or tables, not DROP TABLE or GRANT.
  5. Run Penetration Tests against OWASP Top 10: Schedule bi-annual black-box and white-box penetration tests by independent certified CREST assessors.

10. Distributed Redis Session Management with Concurrent Session Limits

Tracks and limits active sessions per user account, allowing remote session revocation:

// src/services/session.service.ts
import Redis from 'ioredis';
import crypto from 'crypto';

const redis = new Redis(process.env.REDIS_URL!);

export interface SessionData {
  sessionId: string;
  userId: string;
  ipAddress: string;
  userAgent: string;
  createdAt: string;
  lastActiveAt: string;
}

export async function createSession(userId: string, ipAddress: string, userAgent: string, maxConcurrentSessions: number = 5): Promise<string> {
  const sessionId = crypto.randomBytes(32).toString('hex');
  const userSessionsKey = `user_sessions:${userId}`;
  const sessionKey = `session:${sessionId}`;

  const sessionData: SessionData = {
    sessionId,
    userId,
    ipAddress,
    userAgent,
    createdAt: new Date().toISOString(),
    lastActiveAt: new Date().toISOString(),
  };

  // 1. Store session payload with 24-hour expiration
  await redis.set(sessionKey, JSON.stringify(sessionData), 'EX', 86400);

  // 2. Add to user active session set
  await redis.zadd(userSessionsKey, Date.now(), sessionId);

  // 3. Enforce maximum concurrent active sessions (evict oldest)
  const totalSessions = await redis.zcard(userSessionsKey);
  if (totalSessions > maxConcurrentSessions) {
    const oldestSessions = await redis.zrange(userSessionsKey, 0, totalSessions - maxConcurrentSessions - 1);
    for (const oldId of oldestSessions) {
      await redis.del(`session:${oldId}`);
      await redis.zrem(userSessionsKey, oldId);
    }
  }

  return sessionId;
}

export async function validateSession(sessionId: string): Promise<SessionData | null> {
  const raw = await redis.get(`session:${sessionId}`);
  if (!raw) return null;

  const data: SessionData = JSON.parse(raw);
  // Slide session window (extend TTL on activity)
  await redis.expire(`session:${sessionId}`, 86400);
  return data;
}

export async function revokeAllUserSessions(userId: string) {
  const userSessionsKey = `user_sessions:${userId}`;
  const sessionIds = await redis.zrange(userSessionsKey, 0, -1);

  for (const id of sessionIds) {
    await redis.del(`session:${id}`);
  }
  await redis.del(userSessionsKey);
}

11. Cryptographic Hash Function Selection Framework

Algorithm Primary Use Case Work Factor Type Vulnerabilities / Status
Argon2id User Password Hashing Memory-Hard & Time-Hard Gold Standard (RFC 9106, OWASP Preferred)
bcrypt Password Storage (Legacy) Time-Hard (CPU intensive) Susceptible to modern FPGA/ASIC acceleration
PBKDF2 FIPS-140 Compliance Iteration Count Vulnerable to GPU parallelization; requires >= 600,000 rounds
SHA-256 / SHA-3 Digital Signatures & Data Integrity Fast / Non-Iterative Never use for passwords (computed at billions/sec on GPUs)
MD5 / SHA-1 Checksums (Legacy) Obsolete Cryptographically broken; collision attacks trivial

12. Security Incident Response & Forensics Playbook

When an active data breach or credential leak is suspected:

  1. Rotate All Master Secrets Immediately: Issue emergency rotation of database master passwords, JWT signing keys, and external API tokens.
  2. Revoke Active User Sessions Globally: Flush active session registries (revokeAllUserSessions) and invalidate refresh token families.
  3. Inspect Gateway Access Logs for Lateral Movement: Grep reverse proxy access logs for correlated X-Request-ID and source IP addresses.
  4. Isolate Affected Pods and Take Memory Snapshots: Remove breached container instances from Kubernetes Service endpoints without destroying the pods to preserve volatile RAM evidence for forensic analysis.

13. OAuth 2.0 Proof Key for Code Exchange (PKCE) Implementation

Protects Single Page Applications (SPAs) and mobile native apps against authorization code interception attacks:

// src/utils/pkce.ts
import crypto from 'crypto';

// 1. Base64URL encoding helper
function base64UrlEncode(buffer: Buffer): string {
  return buffer
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

// 2. Generate cryptographically random code verifier (43-128 characters)
export function generateCodeVerifier(): string {
  return base64UrlEncode(crypto.randomBytes(32));
}

// 3. Compute code challenge from verifier using SHA-256
export function generateCodeChallenge(verifier: string): string {
  const hash = crypto.createHash('sha256').update(verifier).digest();
  return base64UrlEncode(hash);
}

// 4. Verify code verifier against stored challenge on token exchange
export function verifyCodeChallenge(verifier: string, storedChallenge: string): boolean {
  const calculatedChallenge = generateCodeChallenge(verifier);
  return crypto.timingSafeEqual(
    Buffer.from(calculatedChallenge),
    Buffer.from(storedChallenge)
  );
}

14. Complete OWASP Top 10 Defensive Summary Checklist

OWASP Risk Category Primary Vector Prescribed Defensive Architecture
A01: Broken Access Control IDOR / BOLA parameter tampering Row-Level Security (RLS) & context-scoped DB queries
A02: Cryptographic Failures Plaintext secrets & weak ciphers Argon2id for passwords; AES-256-GCM; TLS 1.3 only
A03: Injection SQL / Command string interpolation Prepared statements with AST parameterization
A04: Insecure Design Missing business limits & brute-force Sliding-window rate limiting; multi-factor authentication
A05: Security Misconfiguration Verbose headers & open ports Helmet headers; noSniff; disabled server banners
A06: Vulnerable Components Stale NPM / container CVEs Automated daily Dependabot & Snyk vulnerability scans
A07: Identification & Auth Credential stuffing & session hijack Passkeys / WebAuthn; __Host- cookies; rotation
A08: Software & Data Integrity Untrusted CI/CD code & tampered CDN Subresource Integrity (SRI); signed commits
A09: Logging & Monitoring Silent compromise without alerts Audit trail logs; centralized SIEM; Prometheus alerts
A10: SSRF Metadata scraping (169.254.169.254) Pre-resolve DNS; drop private CIDRs (10.0.0.0/8)

Architecture & Standards

Maintained by Manthan Ank. Compliant with NIST SP 800-63B (Digital Identity Guidelines) and OWASP Application Security Verification Standard (ASVS) Level 3.

15. WebAuthn / Passkeys Architecture (FIDO2 Modern Authentication)

Eliminates password phishing completely through hardware-bound cryptographic key pairs (Touch ID, Face ID, YubiKey):

// src/services/webauthn.service.ts
import crypto from 'crypto';

export interface RegistrationOptions {
  challenge: string;
  rp: { name: string; id: string };
  user: { id: string; name: string; displayName: string };
  pubKeyCredParams: Array<{ alg: number; type: 'public-key' }>;
  authenticatorSelection: {
    authenticatorAttachment?: 'platform' | 'cross-platform';
    userVerification: 'required' | 'preferred';
    residentKey: 'required';
  };
  timeout: number;
}

export function generateRegistrationOptions(userId: string, userEmail: string, rpId: string): RegistrationOptions {
  // Generate 256-bit cryptographically secure challenge
  const challenge = crypto.randomBytes(32).toString('base64url');

  return {
    challenge,
    rp: {
      name: 'HubScale Enterprise Security',
      id: rpId, // e.g. 'hubscale.io'
    },
    user: {
      id: Buffer.from(userId).toString('base64url'),
      name: userEmail,
      displayName: userEmail.split('@')[0],
    },
    pubKeyCredParams: [
      { alg: -7, type: 'public-key' },  // ES256 (ECDSA w/ SHA-256)
      { alg: -257, type: 'public-key' }, // RS256 (RSASSA-PKCS1-v1_5 w/ SHA-256)
    ],
    authenticatorSelection: {
      authenticatorAttachment: 'platform',
      userVerification: 'required',
      residentKey: 'required',
    },
    timeout: 60000,
  };
}

export function generateAuthenticationOptions(rpId: string): { challenge: string; rpId: string; timeout: number } {
  return {
    challenge: crypto.randomBytes(32).toString('base64url'),
    rpId,
    timeout: 60000,
  };
}

export function verifyPasskeySignature(clientDataJSON: string, authenticatorData: Buffer, signature: Buffer, publicKeyPem: string): boolean {
  const clientDataHash = crypto.createHash('sha256').update(clientDataJSON).digest();
  const signatureBase = Buffer.concat([authenticatorData, clientDataHash]);

  const verifier = crypto.createVerify('SHA256');
  verifier.update(signatureBase);
  return verifier.verify(publicKeyPem, signature);
}

7. Stage 7: Staff Security Architect Interview Handbook & Production Cheatsheet

50 Staff-Level Web Application Security Interview Questions & Answers

Q1: What is the Same-Origin Policy (SOP) and why does it not block cross-origin writes by default?

Answer: The Same-Origin Policy prevents a script loaded from Origin A from reading confidential response data returned by Origin B. Historically, HTML allowed cross-origin writes via <form method="POST"> and <img> tags before SOP was formalized. While browsers prevent Origin A from inspecting the response of a cross-origin request, the request is still dispatched to the network and executed on the target server. This fundamental asymmetry is the exact vulnerability exploited by CSRF.

Q2: What is the difference between Stored, Reflected, and DOM-based XSS?

Answer: Stored XSS occurs when malicious input is persisted permanently in database storage and served to subsequent users inside server-rendered HTML. Reflected XSS occurs when input from a URL or form is immediately mirrored into the HTTP response page without sanitization. DOM-based XSS occurs entirely on the client side: the server sends clean HTML, but client-side JavaScript reads from an untrusted source (location.search, document.referrer) and passes it to an execution sink (element.innerHTML, eval()).

Q3: How does Content Security Policy (CSP v3) strict-dynamic work?

Answer: In CSP v3, 'strict-dynamic' combined with a cryptographically secure random nonce ('nonce-XYZ') allows any script that carries the valid nonce to dynamically create and append new child <script> elements to the DOM. Those dynamically generated child scripts are automatically trusted and executed by the browser without needing separate individual nonces or maintaining fragile domain whitelists.

Q4: What is the difference between SameSite=Strict, SameSite=Lax, and SameSite=None on cookies?

Answer:

  • Strict: The cookie is never sent in cross-site requests, even when following a top-level link from an external website (e.g. clicking a link to your dashboard from an email).
  • Lax: The cookie is withheld on cross-site sub-requests (images, iframes, cross-origin POST requests), but is sent when navigating via top-level safe GET requests (clicking an external link).
  • None: The cookie is sent on all cross-site requests, requiring the Secure flag (Secure; SameSite=None).

Q5: What is BOLA (Broken Object Level Authorization) and how is it prevented?

Answer: BOLA (formerly IDOR - Insecure Direct Object References) occurs when an endpoint accepts a resource identifier (/api/documents/1024) and retrieves the resource from the database without validating that the authenticated session user actually owns or is authorized to view that specific record. Prevention: Enforce authorization checks in every database query (e.g. WHERE id = :documentId AND organization_id = :sessionOrgId) or utilize Row-Level Security (RLS) in the database engine.

Q6: Why is Argon2id preferred over bcrypt and PBKDF2 for password storage?

Answer: Bcrypt and PBKDF2 are computationally intensive (time-hard) but require virtually zero memory. Attackers can build massive parallelized FPGA and ASIC cracking rigs that compute billions of bcrypt hashes per second. Argon2id is a memory-hard algorithm requiring dozens of megabytes of RAM per hash evaluation, making ASIC and GPU parallelization financially and physically prohibitive for attackers.

Q7: What is the alg: none vulnerability in JWT implementations?

Answer: The JSON Web Token specification allows an algorithm header value of "alg": "none", designed for unauthenticated testing. Vulnerable verification libraries that did not enforce explicit algorithm whitelists accepted modified tokens where the signature was stripped and alg was set to "none", allowing attackers to forge administrative claims ({ "role": "admin" }) that were treated as valid.

Q8: What is DNS Rebinding and how does it bypass basic SSRF IP checks?

Answer: An attacker registers a domain whose DNS server returns a public IP with a TTL of 1 second on the initial resolution check (passing the backend validation filter). A fraction of a second later, when the backend actually executes the HTTP request, the DNS server responds with 127.0.0.1 or 169.254.169.254. Prevention: Resolve the IP address once, validate it, and connect directly to the resolved IP address over a pinned socket or disable DNS lookups during request execution.

Q9: What is Clickjacking and how is it completely prevented?

Answer: Clickjacking tricks a user into clicking invisible or disguised UI elements by loading the vulnerable application inside a transparent <iframe> overlaid on an enticing game or button. Prevention: Send the Content-Security-Policy: frame-ancestors 'none'; header (or legacy X-Frame-Options: DENY), which instructs browsers to block framing of the page entirely.

Q10: What are the __Host- and __Secure- cookie prefixes?

Answer: Browser-enforced security flags:

  • __Secure-: Rejects cookie creation unless the Secure flag is present (HTTPS required).
  • __Host-: Rejects cookie creation unless Secure is present, Path=/ is specified, and the Domain attribute is omitted (preventing malicious subdomains from overwriting parent cookies).

Q11: What is the difference between Content-Type sniffing and X-Content-Type-Options: nosniff?

Answer: Without nosniff, browsers attempt to inspect (sniff) file payloads. If a user uploads an avatar image containing <script>alert(1)</script>, the browser might execute it as HTML. X-Content-Type-Options: nosniff forces the browser to respect the declared Content-Type strictly.

Q12: How does HSTS (HTTP Strict Transport Security) protect against SSL Stripping?

Answer: Sends Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. The browser caches this directive and automatically upgrades all subsequent http:// requests to https:// internally before sending packets over the network, rendering man-in-the-middle SSL stripping attacks impossible.

Q13: What is Subresource Integrity (SRI)?

Answer: A security feature allowing browsers to verify that resources fetched from CDNs (scripts, stylesheets) have not been modified or compromised: <script src="https://cdn.example.com/lib.js" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" crossorigin="anonymous"></script>.

Q14: What is the difference between Authentication and Authorization?

Answer: Authentication verifies who the entity is (identity verification via password, MFA, passkey). Authorization determines what permissions and actions the authenticated entity is permitted to execute (RBAC, ABAC).

Q15: What is the Synchronizer Token Pattern for CSRF defense?

Answer: The server generates a unique, unpredictable cryptographically random token tied to the user's current session and embeds it in HTML forms or sends it via custom header. When the form is submitted, the server compares the submitted token against the session token; cross-site requests cannot read or forge this token due to SOP.

Q16: What is a Timing Attack in password or token verification?

Answer: Standard string comparison (str1 === str2) returns false on the very first mismatched character, taking slightly less time for wrong characters than right ones. Attackers measure execution latency variations down to nanoseconds to guess secret tokens character-by-character. Prevention: Use constant-time comparison algorithms like crypto.timingSafeEqual().

Q17: What is the purpose of the Permissions-Policy header?

Answer: Controls which browser hardware features and APIs (camera, microphone, geolocation, payment request) can be used by the document or embedded iframes: Permissions-Policy: camera=(), microphone=(), geolocation=().

Q18: What is SQL Injection and why are ORMs not a 100% guarantee against it?

Answer: Concatenating unsanitized user input into database query strings alters the AST grammar of the query. While ORMs parameterize standard queries, developers can still introduce SQL injection vulnerabilities if they use raw query methods (prisma.$queryRawUnsafe, db.execute(sql"...") with string concatenation) with untrusted input.

Q19: What is Open Redirect vulnerability?

Answer: An endpoint that accepts a redirect URL parameter (/login?redirect=https://evil.com) and redirects the user without validating that the target belongs to a trusted domain whitelist, enabling phishing attacks using trusted brand domains.

Q20: What is CORS Preflight caching and how is it configured?

Answer: In response to an OPTIONS preflight request, the server includes Access-Control-Max-Age: 86400, instructing the browser to cache the CORS permission grant for 24 hours, avoiding extra network round-trips for subsequent requests.

Q21: What is Path Traversal (Directory Traversal)?

Answer: An attacker accesses arbitrary files on the filesystem by manipulating input paths using ../ sequences (e.g., GET /download?file=../../../../etc/passwd). Prevention: Resolve paths with path.resolve(), verify they reside within the root directory, and validate against a strict alphanumeric whitelist.

Q22: What is the purpose of the Referrer-Policy header?

Answer: Controls how much referrer information (URL path and query parameters) is included in the Referer header when navigating between pages or requesting external assets. Modern standard: Referrer-Policy: strict-origin-when-cross-origin.

Q23: What is the difference between symmetric and asymmetric encryption?

Answer: Symmetric encryption (e.g. AES-256-GCM) uses the identical secret key for both encryption and decryption. Asymmetric encryption (e.g. RSA-4096, Ed25519) uses a mathematically linked key pair: a public key for encryption/signature verification and a private key for decryption/signing.

Q24: What is Server-Side Template Injection (SSTI)?

Answer: Occurs when user input is concatenated directly into template engine templates (Jinja2, Handlebars, EJS) instead of passed as data variables, allowing attackers to execute arbitrary code on the server via template syntax directives.

Q25: What is Replay Attack and how is it mitigated in REST APIs?

Answer: An attacker intercepts a legitimate request over the wire and repeats it to re-execute an action (e.g. duplicate payment). Mitigation: Include a timestamp and unique nonce in request signatures, rejecting requests with expired timestamps or repeated nonces.

Q26: What is the purpose of OAuth 2.0 PKCE (Proof Key for Code Exchange)?

Answer: Eliminates authorization code interception attacks on public clients (mobile apps, Single Page Applications) where client secrets cannot be stored securely, replacing secrets with a dynamically generated code_verifier and hashed code_challenge.

Q27: How does HTTP Parameter Pollution (HPP) occur?

Answer: Sending duplicate query parameters (?role=user&role=admin). Depending on how backend frameworks parse arrays vs strings, authorization filters might check the first parameter while database operations execute using the second.

Q28: What is Credential Stuffing and how is it defended against?

Answer: Automated injection of stolen username/password pairs from third-party data breaches against your login portal. Defense: Multi-Factor Authentication (MFA), CAPTCHA challenges on velocity thresholds, rate limiting by IP/account, and HaveIBeenPwned API breach checks on password creation.

Q29: What is the Trusted Types API?

Answer: A modern browser security feature enforced via CSP (require-trusted-types-for 'script') that locks down dangerous DOM execution sinks (innerHTML, script.src), requiring values to be wrapped in cryptographically trusted type wrappers created by vetted sanitization policies.

Q30: What is Broken Authentication in OWASP?

Answer: Vulnerabilities that permit attackers to compromise passwords, session tokens, or exploit implementation flaws (session fixation, missing MFA, session identifiers in URLs).

Q31: What is the difference between Session Fixation and Session Hijacking?

Answer: Session Fixation: The attacker tricks the victim into using a known session identifier provided by the attacker before login. Session Hijacking: The attacker steals an active session token after the victim has already authenticated.

Q32: What is an Insecure Deserialization attack?

Answer: When untrusted serialized objects (Java serialization, Python pickle, PHP serialize) are deserialized by the backend, allowing attackers to manipulate object state or trigger remote code execution (RCE) via gadget chains.

Q33: What is Mass Assignment vulnerability?

Answer: When an application automatically binds all client request fields directly to database model properties (e.g. User.update(req.body)), allowing an attacker to inject privileged fields like isAdmin: true or verified: true.

Q34: What is the principle of Least Privilege?

Answer: Every module, process, user, and service account must possess only the bare minimum permissions required to perform its valid function, minimizing blast radius upon compromise.

Q35: What is Cross-Site Script Inclusion (XSSI)?

Answer: Stealing sensitive data from dynamic JSON endpoints by including them via <script src="..."> tags on an attacker's domain. Defense: Use Content-Type: application/json and prefix JSON responses with )]}', to cause syntax errors if parsed as a script.

Q36: What is a Side-Channel Attack?

Answer: An attack based on information gained from the implementation of a computer system (timing variations, power consumption, electromagnetic leaks) rather than weaknesses in the algorithm itself.

Q37: What is Mutual TLS (mTLS)?

Answer: A cryptographic protocol where both client and server authenticate each other using X.509 digital certificates, ensuring zero-trust microservice communication within service meshes.

Q38: How do you prevent XML External Entity (XXE) attacks?

Answer: Disable XML external entity resolution and DTD processing in XML parsers (disallow-doctype-decl = true).

Q39: What is Rate Limiting and what are its standard algorithms?

Answer: Restricting request volume over time. Common algorithms: Token Bucket, Leaky Bucket, Fixed Window Counter, and Sliding Window Log.

Q40: What is Security by Obscurity and why is it flawed?

Answer: Relying on the secrecy of implementation details (e.g., hiding admin URLs, using non-standard ports) instead of robust cryptographic security. Kerckhoffs's principle dictates that a system must remain secure even if everything about the system is public knowledge except the key.

Q41: What is an IDOR vulnerability?

Answer: Insecure Direct Object Reference: an access control flaw where an application exposes a reference to an internal object (database ID, filename) without verifying user authorization.

Q42: What is the purpose of Clear-Site-Data header?

Answer: Clears browser storage (cookies, cache, storage, execution contexts) upon logout: Clear-Site-Data: "cache", "cookies", "storage".

Q43: How does JWT signature verification work?

Answer: The header and payload are hashed together with a secret key or verified against a public key. If any byte in the header or payload changes, the signature verification fails immediately.

Q44: What is Server-Side Request Forgery (SSRF)?

Answer: Inducing a backend server to make unauthorized HTTP or socket requests to internal infrastructure or external systems on behalf of the attacker.

Q45: What is Content-Security-Policy report-uri / report-to?

Answer: Configures an endpoint where browsers send automated JSON violation reports when a CSP policy violation is detected or blocked.

Q46: What is a ReDoS (Regular Expression Denial of Service) attack?

Answer: Crafting malicious input that exploits catastrophic backtracking in polynomial or exponential regular expressions, consuming 100% CPU and halting the event loop.

Q47: What is Zero Trust Architecture?

Answer: A security model based on the principle "never trust, always verify": all requests, whether from outside or inside the corporate network, must be fully authenticated, authorized, and encrypted before granting access.

Q48: How do you prevent prototype pollution in JavaScript?

Answer: Validate object keys to disallow __proto__, constructor, and prototype, use Object.create(null) for key-value dictionaries, or freeze prototypes with Object.freeze(Object.prototype).

Q49: What is SAST vs DAST in AppSec pipelines?

Answer:

  • SAST (Static Application Security Testing): Analyzes source code for security flaws without executing the code.
  • DAST (Dynamic Application Security Testing): Tests running applications from the outside by executing automated simulated attacks.

Q50: Why is security considered a continuous engineering lifecycle rather than a one-time audit?

Answer: Software systems continuously evolve through dependency updates, infrastructure mutations, and emerging zero-day vulnerabilities. True enterprise security demands automated CI/CD guardrails, threat modeling, dependency scanning, runtime observability, and regular penetration testing.


Production Web Security Cheatsheet

Defensive Control Implementation Standard
XSS Defense Strict CSP v3 (script-src 'nonce-...' 'strict-dynamic'), DOMPurify, Trusted Types
CSRF Defense SameSite=Strict cookies, Synchronizer Token Pattern
Clickjacking Content-Security-Policy: frame-ancestors 'none';
Password Hashing Argon2id (memoryCost: 65536, timeCost: 3, parallelism: 4)
Session Cookies __Host- prefix, Secure, HttpOnly, SameSite=Strict, Path=/
SSRF Guard Pre-resolve DNS, block private CIDRs (10.0.0.0/8, 169.254.0.0/16, 127.0.0.0/8)
MIME Sniffing X-Content-Type-Options: nosniff
Transport Security Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Contributing & License

Maintained with ❤️ by Manthan Ank. Distributed under the MIT License.

About

Master Web Security: OWASP Top 10, CORS, CSP, CSRF, XSS defense, rate limiting, and cryptographic foundations

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages