Episode 6 — Scaling Reliability Microservices Web3 / 6.8 — Production Hardening

6.8.c — DDoS Protection

In one sentence: A DDoS (Distributed Denial of Service) attack overwhelms your system from thousands of sources simultaneously — defending against it requires layered protection from the network edge all the way down to your application code.

Navigation: ← 6.8.b — CORS and Secure Headers · 6.8 Overview →


1. What Is a DDoS Attack?

A Distributed Denial of Service attack floods your infrastructure with so much traffic that legitimate users cannot access your service. Unlike a DoS (single source), DDoS comes from thousands or millions of compromised devices (a botnet).

Normal Traffic:

  User A ──→ ┐
  User B ──→ ├──→ Server ──→ ✅ All served
  User C ──→ ┘

DDoS Attack:

  Bot 1    ──→ ┐
  Bot 2    ──→ │
  Bot 3    ──→ │
  ...          ├──→ Server ──→ ✗ Overwhelmed
  Bot 9998 ──→ │                 Legitimate users
  Bot 9999 ──→ │                 cannot connect
  Bot 10000──→ ┘
  User A   ──→ ✗ Connection refused / timeout

2. DDoS Attack Types

2.1 Volumetric Attacks (Layer 3/4)

Flood the network with massive amounts of data. Goal: saturate bandwidth.

Examples:
  - UDP Flood:     Send massive UDP packets to random ports
  - ICMP Flood:    Overwhelm with ping packets
  - DNS Amplification: Tiny request → huge response reflected at target

Scale: 100 Gbps to 3+ Tbps (terabits per second)

Defense: Can only be stopped at the network edge (CDN, ISP)

2.2 Protocol Attacks (Layer 3/4)

Exploit protocol weaknesses to exhaust server resources (connection tables, firewalls).

Examples:
  - SYN Flood:     Send thousands of TCP SYN packets, never complete handshake
  - Ping of Death:  Malformed oversized ICMP packets
  - Smurf Attack:   Broadcast ICMP to amplify traffic

Target: Connection state tables, load balancers, firewalls
SYN Flood:

  Attacker                    Server
     │ SYN ──────────────────→ │  Connection table: [ HALF_OPEN ]
     │ SYN ──────────────────→ │  Connection table: [ HALF_OPEN, HALF_OPEN ]
     │ SYN ──────────────────→ │  Connection table: [ HALF_OPEN, HALF_OPEN, ... ]
     │ ...                     │
     │ SYN ──────────────────→ │  Connection table: FULL → new connections rejected
     │ (never sends ACK)       │
                               │  Legitimate user SYN → REJECTED (no room)

2.3 Application Layer Attacks (Layer 7)

Target your application logic with seemingly legitimate HTTP requests.

Examples:
  - HTTP Flood:         Thousands of GET /api/search?q=... requests
  - Slowloris:          Open connections, send headers very slowly
  - POST Flood:         Submit large forms/JSON payloads repeatedly
  - API Abuse:          Hit expensive endpoints (search, reports, AI)

Why they're dangerous:
  - Look like legitimate traffic (hard to distinguish)
  - Small bandwidth but high server CPU/DB cost
  - WAF and CDN may pass them through
Slowloris Attack:

  Attacker opens 1000 connections:
  
  Connection 1: "GET / HTTP/1.1\r\nHost: example.com\r\nX-a: b\r\n"
                 ... waits 10 seconds ...
                 "X-c: d\r\n"       ← sends another header, keeps alive
                 ... waits 10 seconds ...
                 "X-e: f\r\n"       ← never sends final \r\n\r\n

  Connection 2: same pattern
  ...
  Connection 1000: same pattern

  Result: All server connections occupied → legitimate users get "503"

3. DDoS Protection Layers

┌──────────────────────────────────────────────────────────────────────┐
│                    DEFENSE IN DEPTH                                    │
│                                                                        │
│  Layer 1: DNS / CDN Edge                                               │
│  ┌──────────────────────────────────────────────────────────┐          │
│  │  CloudFront / CloudFlare                                  │          │
│  │  - Absorbs volumetric attacks (Tbps capacity)             │          │
│  │  - Geographic distribution                                │          │
│  │  - TLS termination                                        │          │
│  └────────────────────────┬─────────────────────────────────┘          │
│                           ▼                                            │
│  Layer 2: AWS Shield                                                   │
│  ┌──────────────────────────────────────────────────────────┐          │
│  │  Shield Standard (free) / Shield Advanced (paid)          │          │
│  │  - Automatic L3/L4 attack mitigation                      │          │
│  │  - DDoS Response Team (Advanced)                          │          │
│  │  - Cost protection (Advanced)                             │          │
│  └────────────────────────┬─────────────────────────────────┘          │
│                           ▼                                            │
│  Layer 3: WAF (Web Application Firewall)                               │
│  ┌──────────────────────────────────────────────────────────┐          │
│  │  AWS WAF / CloudFlare WAF                                 │          │
│  │  - SQL injection rules                                    │          │
│  │  - XSS pattern blocking                                   │          │
│  │  - Rate-based rules (L7)                                  │          │
│  │  - Geo-blocking                                           │          │
│  │  - Bot detection                                          │          │
│  └────────────────────────┬─────────────────────────────────┘          │
│                           ▼                                            │
│  Layer 4: Application Rate Limiting                                    │
│  ┌──────────────────────────────────────────────────────────┐          │
│  │  express-rate-limit / Redis-based limiting                │          │
│  │  - Per-user / per-IP / per-key quotas                     │          │
│  │  - Endpoint-specific limits                               │          │
│  └────────────────────────┬─────────────────────────────────┘          │
│                           ▼                                            │
│  Layer 5: Application Hardening                                        │
│  ┌──────────────────────────────────────────────────────────┐          │
│  │  Input validation, connection limits, request size caps   │          │
│  │  Timeout enforcement, queue-based processing              │          │
│  └──────────────────────────────────────────────────────────┘          │
└──────────────────────────────────────────────────────────────────────┘

4. AWS Shield

Shield Standard (Free)

Automatically included with all AWS services. Protects against most common L3/L4 DDoS attacks.

  • Automatic detection — always-on monitoring
  • Inline mitigation — absorbs SYN floods, UDP floods, reflection attacks
  • No configuration needed — works on CloudFront, Route 53, ALB, ELB
  • No extra cost

Shield Advanced ($3,000/month)

For mission-critical applications. Includes everything in Standard plus:

FeatureWhat It Does
DDoS Response Team (DRT)24/7 team that helps during active attacks
Advanced detectionTailored to your traffic patterns
Cost protectionAWS credits for DDoS-related scaling costs
Real-time metricsCloudWatch dashboards for attack visibility
WAF integrationAuto-creates WAF rules during attacks
Health-based detectionUses Route 53 health checks for faster response
When to use Shield Advanced:
  - Revenue-critical applications (e-commerce, banking)
  - Applications that cannot tolerate ANY downtime
  - When DDoS-related auto-scaling would cost more than $3K/month
  - Regulatory requirements for DDoS protection

5. CloudFront as DDoS Protection

CloudFront is not just a CDN — it is your first line of DDoS defense.

How CloudFront absorbs attacks:

  Attack traffic → 600+ Edge Locations worldwide
                   │
                   ▼
              ┌────────────────┐
              │  CloudFront     │
              │  Edge Location  │
              │                 │
              │  - Massive      │
              │    bandwidth    │
              │  - Shield       │
              │    Standard     │
              │  - Can cache    │
              │    responses    │
              │  - WAF at edge  │
              └───────┬────────┘
                      │ Only legitimate
                      │ traffic passes
                      ▼
              ┌────────────────┐
              │  Your Origin    │
              │  (ALB/EC2/ECS) │
              └────────────────┘

Key CloudFront DDoS Features

// CloudFront distribution config (conceptual — AWS CDK)
const distribution = new cloudfront.Distribution(this, 'Dist', {
  defaultBehavior: {
    origin: new origins.HttpOrigin('api.example.com'),
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
    // Attach WAF
    webAclId: wafWebAcl.attrArn,
  },
  // Geo-restriction
  geoRestriction: cloudfront.GeoRestriction.allowlist('US', 'CA', 'GB', 'DE'),
  // Custom error responses (don't reveal origin errors)
  errorResponses: [
    {
      httpStatus: 503,
      responseHttpStatus: 503,
      responsePagePath: '/error.html',
      ttl: Duration.seconds(10),
    },
  ],
});

6. WAF (Web Application Firewall)

AWS WAF inspects HTTP/HTTPS requests and decides whether to allow, block, or count them based on rules.

WAF Rule Types

Rule TypeWhat It BlocksExample
IP-basedKnown bad IPs, IP reputation listsBlock IPs from threat intelligence feeds
Rate-basedToo many requests from one IPBlock IP if > 2000 req / 5 min
String matchPatterns in request (SQLi, XSS)Block if query string contains UNION SELECT
RegexComplex pattern matchingBlock unusual user-agent patterns
Geo-matchRequests from specific countriesBlock all traffic from countries you don't serve
Size constraintOversized requestsBlock request body > 8KB
Managed rulesAWS/partner pre-built rule setsOWASP Top 10, Bot Control, Known Bad Inputs

WAF Configuration Example

// AWS WAF Web ACL (conceptual — AWS CDK)
const webAcl = new wafv2.CfnWebACL(this, 'WebACL', {
  defaultAction: { allow: {} },
  scope: 'CLOUDFRONT',
  visibilityConfig: {
    sampledRequestsEnabled: true,
    cloudWatchMetricsEnabled: true,
    metricName: 'productionWAF',
  },
  rules: [
    // Rule 1: Rate limiting
    {
      name: 'RateLimit',
      priority: 1,
      action: { block: {} },
      statement: {
        rateBasedStatement: {
          limit: 2000,           // 2000 requests per 5 minutes
          aggregateKeyType: 'IP',
        },
      },
      visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudWatchMetricsEnabled: true,
        metricName: 'RateLimitRule',
      },
    },
    // Rule 2: AWS Managed Rules — SQL injection
    {
      name: 'SQLInjection',
      priority: 2,
      overrideAction: { none: {} },
      statement: {
        managedRuleGroupStatement: {
          vendorName: 'AWS',
          name: 'AWSManagedRulesSQLiRuleSet',
        },
      },
      visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudWatchMetricsEnabled: true,
        metricName: 'SQLiRule',
      },
    },
    // Rule 3: AWS Managed Rules — Known bad inputs
    {
      name: 'KnownBadInputs',
      priority: 3,
      overrideAction: { none: {} },
      statement: {
        managedRuleGroupStatement: {
          vendorName: 'AWS',
          name: 'AWSManagedRulesKnownBadInputsRuleSet',
        },
      },
      visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudWatchMetricsEnabled: true,
        metricName: 'KnownBadInputsRule',
      },
    },
    // Rule 4: Geo-blocking
    {
      name: 'GeoBlock',
      priority: 4,
      action: { block: {} },
      statement: {
        notStatement: {
          statement: {
            geoMatchStatement: {
              countryCodes: ['US', 'CA', 'GB', 'DE', 'FR', 'AU'],
            },
          },
        },
      },
      visibilityConfig: {
        sampledRequestsEnabled: true,
        cloudWatchMetricsEnabled: true,
        metricName: 'GeoBlockRule',
      },
    },
  ],
});

7. Application-Level DDoS Mitigation

Even with CDN, Shield, and WAF, your application code must be resilient.

7.1 Connection Limiting

import express from 'express';

const app = express();

// Track active connections
let activeConnections = 0;
const MAX_CONNECTIONS = 1000;

const server = app.listen(3000);

server.on('connection', (socket) => {
  activeConnections++;

  if (activeConnections > MAX_CONNECTIONS) {
    socket.destroy();  // Refuse new connections
    return;
  }

  socket.on('close', () => {
    activeConnections--;
  });
});

7.2 Request Size Limits

// Limit JSON body size
app.use(express.json({ limit: '1mb' }));

// Limit URL-encoded body size
app.use(express.urlencoded({ limit: '1mb', extended: true }));

// Limit file uploads
import multer from 'multer';
const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024,  // 5MB max
    files: 5,                    // Max 5 files
    fields: 20,                  // Max 20 non-file fields
  },
});

7.3 Request Timeout Enforcement

// Timeout middleware — kill slow requests
function requestTimeout(limitMs) {
  return (req, res, next) => {
    const timer = setTimeout(() => {
      if (!res.headersSent) {
        res.status(408).json({ error: 'Request timeout' });
      }
    }, limitMs);

    res.on('finish', () => clearTimeout(timer));
    res.on('close', () => clearTimeout(timer));
    next();
  };
}

// 30-second timeout for normal requests
app.use(requestTimeout(30000));

// 5-second timeout for health checks
app.get('/health', requestTimeout(5000), (req, res) => {
  res.json({ status: 'ok' });
});

7.4 Slowloris Protection

// Set server-level timeouts to prevent Slowloris
const server = app.listen(3000);

server.headersTimeout = 20000;    // 20s to receive full headers
server.requestTimeout = 30000;    // 30s to receive full request
server.keepAliveTimeout = 5000;   // Close idle keep-alive connections
server.timeout = 60000;           // 60s total request timeout

7.5 Queue-Based Processing for Expensive Endpoints

import Bull from 'bull';

const reportQueue = new Bull('report-generation', {
  redis: process.env.REDIS_URL,
  limiter: {
    max: 10,        // Process at most 10 jobs
    duration: 60000, // per minute
  },
});

// Instead of processing inline, queue the work
app.post('/api/reports/generate', async (req, res) => {
  const job = await reportQueue.add({
    userId: req.user.id,
    params: req.body,
  });

  res.status(202).json({
    message: 'Report queued',
    jobId: job.id,
    statusUrl: `/api/reports/status/${job.id}`,
  });
});

// Client polls for result
app.get('/api/reports/status/:jobId', async (req, res) => {
  const job = await reportQueue.getJob(req.params.jobId);
  if (!job) return res.status(404).json({ error: 'Job not found' });

  const state = await job.getState();
  res.json({
    state,
    result: state === 'completed' ? job.returnvalue : null,
  });
});

8. Bot Detection Basics

// Simple bot detection heuristics
function botDetection(req, res, next) {
  const ua = req.headers['user-agent'] || '';

  // Block empty user-agents
  if (!ua) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  // Block known bot patterns (basic)
  const botPatterns = [
    /python-requests/i,
    /curl\//i,
    /wget\//i,
    /scrapy/i,
    /httpclient/i,
  ];

  // Only block on sensitive endpoints — don't block legitimate tools on public APIs
  if (req.path.startsWith('/api/auth/')) {
    for (const pattern of botPatterns) {
      if (pattern.test(ua)) {
        return res.status(403).json({ error: 'Automated access not allowed' });
      }
    }
  }

  next();
}

// For production, use AWS WAF Bot Control or CloudFlare Bot Management
// They use ML-based fingerprinting, not just user-agent strings

9. Geo-Blocking

If you only serve certain regions, block traffic from everywhere else:

import geoip from 'geoip-lite';

const ALLOWED_COUNTRIES = new Set(['US', 'CA', 'GB', 'DE', 'FR', 'AU', 'IN']);

function geoBlock(req, res, next) {
  const ip = req.headers['x-forwarded-for']?.split(',')[0]?.trim()
    || req.socket.remoteAddress;

  const geo = geoip.lookup(ip);

  if (geo && !ALLOWED_COUNTRIES.has(geo.country)) {
    return res.status(403).json({
      error: 'Service not available in your region',
    });
  }

  next();
}

app.use(geoBlock);

Better approach: Do geo-blocking at WAF/CloudFront level — never lets the traffic reach your servers.


10. Complete Multi-Layer Defense Configuration

import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';

const app = express();
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

// --- Layer 1: Trust proxy (behind CloudFront/ALB) ---
app.set('trust proxy', 1);

// --- Layer 2: HTTPS enforcement ---
if (process.env.NODE_ENV === 'production') {
  app.use((req, res, next) => {
    if (req.headers['x-forwarded-proto'] !== 'https') {
      return res.redirect(301, `https://${req.hostname}${req.url}`);
    }
    next();
  });
}

// --- Layer 3: Secure headers ---
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
    },
  },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));

// --- Layer 4: CORS ---
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
  credentials: true,
}));

// --- Layer 5: Request size limits ---
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ limit: '1mb', extended: true }));

// --- Layer 6: Global rate limit ---
app.use(rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 500,
  standardHeaders: true,
  legacyHeaders: false,
  store: new RedisStore({
    sendCommand: (...args) => redis.sendCommand(args),
    prefix: 'rl:global:',
  }),
}));

// --- Layer 7: Strict auth rate limit ---
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
  store: new RedisStore({
    sendCommand: (...args) => redis.sendCommand(args),
    prefix: 'rl:auth:',
  }),
});
app.post('/api/auth/login', authLimiter);
app.post('/api/auth/register', authLimiter);

// --- Layer 8: Request timeout ---
const server = app.listen(3000);
server.headersTimeout = 20000;
server.requestTimeout = 30000;
server.keepAliveTimeout = 5000;
server.timeout = 60000;

// --- Layer 9: Connection tracking ---
let connections = 0;
server.on('connection', (socket) => {
  connections++;
  if (connections > 2000) { socket.destroy(); return; }
  socket.on('close', () => connections--);
});

console.log('Multi-layer hardened server running on :3000');

11. Key Takeaways

  1. DDoS attacks come in three types — volumetric (bandwidth), protocol (connections), and application layer (logic).
  2. Application-layer attacks are the hardest to stop — they look like legitimate traffic.
  3. CloudFront is your first defense — it absorbs volumetric attacks with massive edge capacity.
  4. AWS Shield Standard is free — it handles most L3/L4 attacks automatically.
  5. WAF rules block malicious patterns — SQL injection, XSS, rate-based, and geo-blocking.
  6. Application hardening is the last line — timeouts, connection limits, request size caps, queue-based processing.
  7. Defense in depth — no single layer is enough. Every layer catches what the previous one missed.

Explain-It Challenge

  1. Your API is receiving 50,000 legitimate-looking GET requests per second from 10,000 different IPs. CloudFront passes them through. How do you mitigate this application-layer attack?
  2. Explain why Slowloris is effective against Node.js/Express and how to prevent it.
  3. A client says "we pay for AWS Shield — we don't need rate limiting." Why are they wrong?

Navigation: ← 6.8.b — CORS and Secure Headers · 6.8 Overview →