Chapter 28: Denial of Service and DDoS
"Availability is the most underappreciated element of the CIA triad — until it's gone." — Unknown
Picture this: your API goes down on a Monday morning. Latency spikes through the roof, connections time out, and the load balancer health checks all fail. Inbound traffic has jumped from the usual 500 Mbps to 42 Gbps in under three minutes. The source IPs are scattered across 30,000 unique addresses from 18 countries. This is a volumetric DDoS attack — UDP traffic amplified through open DNS resolvers, saturating a 1 Gbps link forty-two times over.
This chapter breaks down what is happening, why it works, and how to stop it.
What Is a Denial of Service Attack?
A Denial of Service (DoS) attack aims to make a service unavailable to its intended users. Unlike other attacks that target confidentiality or integrity, DoS attacks target availability — the third pillar of the CIA triad.
A Distributed Denial of Service (DDoS) uses many source systems — often a botnet of compromised devices — to generate the attack traffic, making it far harder to mitigate.
graph TD
subgraph DoS["DoS (Single Source)"]
A1["Attacker<br/>(1 host)"] -->|"flood"| T1["Target Server"]
Note1["Easy to block:<br/>firewall the source IP"]
end
subgraph DDoS["DDoS (Distributed)"]
B1["Bot 1"] --> T2["Target Server"]
B2["Bot 2"] --> T2
B3["Bot 3"] --> T2
B4["Bot 4"] --> T2
B5["..."] --> T2
BN["Bot N<br/>(30,000+)"] --> T2
Note2["Can't block individual IPs:<br/>too many, and they include<br/>legitimate users' devices"]
end
style T1 fill:#cc2222,color:#fff
style T2 fill:#cc2222,color:#fff
The Three Categories of DDoS
Every DDoS attack falls into one of three categories, each requiring different mitigation strategies:
graph LR
subgraph Vol["VOLUMETRIC<br/>Layer 3/4"]
V1["Goal: Saturate bandwidth"]
V2["UDP amplification"]
V3["ICMP flood"]
V4["Measure: Gbps / Tbps"]
end
subgraph Proto["PROTOCOL<br/>Layer 3/4"]
P1["Goal: Exhaust state tables"]
P2["SYN flood"]
P3["Fragmented packets"]
P4["Measure: Packets/sec"]
end
subgraph App["APPLICATION<br/>Layer 7"]
A1["Goal: Exhaust app resources"]
A2["HTTP flood"]
A3["Slowloris"]
A4["Measure: Requests/sec"]
end
style Vol fill:#cc3333,color:#fff
style Proto fill:#cc6633,color:#fff
style App fill:#886622,color:#fff
Volumetric Attacks: Drowning the Pipe
UDP Amplification: The Physics of DDoS
The most powerful volumetric attacks exploit a fundamental asymmetry: the attacker sends a small request to a third-party server with the victim's IP as the spoofed source address. The server sends a much larger response to the victim.
sequenceDiagram
participant Attacker
participant Amplifier as Amplifier<br/>(Open DNS Resolver,<br/>NTP Server, Memcached)
participant Victim as Victim Server
Note over Attacker: Sends small queries<br/>with SPOOFED source IP<br/>(victim's IP)
Attacker->>Amplifier: Small query (64 bytes)<br/>src: VICTIM_IP<br/>dst: AMPLIFIER_IP
Note over Amplifier: Server processes query<br/>normally. Sends large<br/>response to "source" IP.
Amplifier->>Victim: Large response (3,000+ bytes)<br/>dst: VICTIM_IP
Note over Attacker: Multiply by thousands<br/>of amplifiers queried<br/>simultaneously
Attacker->>Amplifier: Small query (64 bytes)
Amplifier->>Victim: Large response (3,000+ bytes)
Attacker->>Amplifier: Small query (64 bytes)
Amplifier->>Victim: Large response (3,000+ bytes)
Note over Victim: Victim receives flood of<br/>unsolicited responses<br/>from thousands of amplifiers.<br/>Bandwidth completely saturated.
Amplification Factors by Protocol
| Protocol | Amplification Factor | Mechanism | Notable Attack |
|---|---|---|---|
| Memcached | 10,000 - 51,000x | UDP stats / get returns massive cached data | GitHub 2018: 1.35 Tbps |
| NTP | 556x | monlist returns list of last 600 clients | 2014 NTP amplification wave |
| CLDAP | 56 - 70x | Active Directory LDAP responds with large data | Enterprise-targeted attacks |
| DNS | 28 - 54x | ANY query with EDNS0 returns all records | Spamhaus 2013: 300 Gbps |
| Chargen | 358x | Responds with random characters to any input | Legacy protocol abuse |
| SSDP | 30x | UPnP discovery responses from IoT devices | Consumer router abuse |
| SNMP | 6x | GetBulk requests return large MIB trees | Internal network amplification |
Yes, 51,000x amplification is real. An attacker with 1 Mbps upload can theoretically generate 51 Gbps of attack traffic. The 2018 GitHub DDoS used memcached amplification and peaked at 1.35 Tbps — the largest attack recorded at that time. It came from only about 100,000 memcached servers that were exposed to the internet with their UDP port open. The fix is embarrassingly simple: bind memcached to localhost and disable UDP.
DNS Amplification in Detail
# How DNS amplification works:
# Attacker sends this tiny query (spoofing the victim's IP):
$ dig @open-resolver.example.com ANY example.com +edns=0 +bufsize=4096
# Query size: ~64 bytes
# Response size: ~3,000+ bytes (all DNS records for the domain)
# Amplification: ~47x
# The open resolver sends the 3,000-byte response to the VICTIM
# (because the attacker spoofed the victim's source IP in the query)
# Multiply by thousands of open resolvers queried simultaneously:
# 1,000 resolvers × 3,000 bytes × 100 queries/sec = 300 MB/sec = 2.4 Gbps
# From a single attacker with ~50 Mbps upload
# Find open DNS resolvers (for defense/research):
$ nmap -sU -p 53 --script dns-recursion 192.168.1.0/24
# Any resolver allowing recursive queries from the internet is a potential amplifier
# Defense: configure your DNS resolver to reject external recursive queries
# In BIND named.conf:
options {
recursion yes;
allow-recursion { 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; };
# Only allow recursion from internal networks
};
NTP Amplification
# The monlist command returns the last 600 clients that queried the server
# Query: 234 bytes → Response: ~100 packets × ~480 bytes = ~48,000 bytes
# Amplification factor: ~206x (up to 556x with more clients)
# Check if an NTP server supports monlist:
$ ntpdc -c monlist ntp.example.com
# If it responds with a client list, it's vulnerable
# Fix: disable monlist in ntp.conf
restrict default kod nomodify notrap nopeer noquery
# Or upgrade to ntpd 4.2.7+ where monlist is disabled by default
Memcached Amplification
On February 28, 2018, GitHub was hit by the largest DDoS attack ever recorded at that time — **1.35 Tbps** of inbound traffic. The attack lasted about 20 minutes and used memcached amplification.
**How it worked:**
1. Attackers pre-loaded publicly accessible memcached servers with large data values (filling their caches with junk)
2. Sent small UDP `get` requests to these servers, spoofing GitHub's IP as the source
3. The memcached servers responded to GitHub with the cached data — amplification factor of up to 51,000x
4. GitHub received 1.35 Tbps of unsolicited memcached responses
**Why memcached was so devastating:**
- Designed for trusted internal networks but often exposed to the internet
- Listens on UDP port 11211 by default
- A single `get` command can return megabytes of cached data
- No authentication required
**GitHub's response:**
- Traffic was automatically routed to Akamai Prolexic (their DDoS mitigation provider)
- Prolexic absorbed the traffic across their global scrubbing network
- GitHub was back online within 10 minutes of the attack starting
- Total downtime: approximately 10 minutes
**The fix for memcached:**
```bash
# Check if memcached is exposed (on your own systems only):
$ echo "stats" | nc -u -w1 your-server-ip 11211
# Fix: disable UDP and bind to localhost in /etc/memcached.conf:
-U 0 # Disable UDP entirely
-l 127.0.0.1 # Only listen on localhost
# Or firewall port 11211 from external access
---
## Protocol Attacks: Exhausting State
### SYN Flood: Exploiting the TCP Handshake
The SYN flood exploits the three-way TCP handshake. The attacker sends a flood of SYN packets with spoofed source IPs. The server allocates resources for each half-open connection, waiting for the ACK that will never come.
```mermaid
sequenceDiagram
participant Attacker
participant Server
Note over Attacker,Server: NORMAL TCP HANDSHAKE
Attacker->>Server: SYN (seq=100)
Note over Server: Allocate TCB<br/>(Transmission Control Block)
Server->>Attacker: SYN-ACK (seq=200, ack=101)
Attacker->>Server: ACK (ack=201)
Note over Server: Connection established
Note over Attacker,Server: SYN FLOOD ATTACK
Attacker->>Server: SYN (src: spoofed_IP_1)
Note over Server: Allocate TCB #1
Attacker->>Server: SYN (src: spoofed_IP_2)
Note over Server: Allocate TCB #2
Attacker->>Server: SYN (src: spoofed_IP_3)
Note over Server: Allocate TCB #3
Attacker->>Server: SYN (src: spoofed_IP_4)
Note over Server: Allocate TCB #4
Attacker->>Server: ... (thousands per second)
Note over Server: Backlog queue fills up!<br/>SYN-ACKs sent to spoofed IPs<br/>(they never respond)
Note over Server: RESULT:<br/>Legitimate clients get<br/>"Connection refused"<br/>or timeout
SYN Cookies: The Elegant Defense
SYN cookies eliminate the need to store state for half-open connections. Instead of allocating a TCB when a SYN arrives, the server encodes the connection information in the SYN-ACK's sequence number using a cryptographic hash.
flowchart TD
SYN["Server receives SYN"]
subgraph WithoutCookies["WITHOUT SYN Cookies"]
W1["Allocate memory for TCB"] --> W2["Store client IP, port,<br/>TCP options"]
W2 --> W3["Start retransmit timer"]
W3 --> W4["Send SYN-ACK"]
W4 --> W5["Wait for ACK<br/>(up to 75 seconds)"]
W5 --> W6["RESOURCE CONSUMED<br/>regardless of legitimacy"]
end
subgraph WithCookies["WITH SYN Cookies"]
C1["Compute cryptographic hash:<br/>cookie = hash(src_ip, src_port,<br/>dst_ip, dst_port, timestamp, secret)"]
C1 --> C2["Use cookie as ISN<br/>in SYN-ACK"]
C2 --> C3["Send SYN-ACK"]
C3 --> C4["FORGET about connection<br/>ZERO memory used"]
end
SYN --> WithoutCookies
SYN --> WithCookies
ACK["ACK arrives (ack = cookie + 1)"]
ACK --> Verify{"Recompute hash.<br/>Does it match?"}
Verify -->|"Yes"| Legit["Legitimate connection!<br/>Allocate TCB NOW"]
Verify -->|"No"| Drop["Drop packet"]
style W6 fill:#cc2222,color:#fff
style C4 fill:#228844,color:#fff
style Legit fill:#228844,color:#fff
# Enable SYN cookies on Linux
$ sysctl -w net.ipv4.tcp_syncookies=1
# Make it permanent
$ echo "net.ipv4.tcp_syncookies = 1" >> /etc/sysctl.conf
# Check current SYN backlog queue size
$ sysctl net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_max_syn_backlog = 256
# Increase for legitimate high-traffic servers
$ sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# Monitor SYN flood status in real-time
$ ss -s
TCP: 45000 (estab 200, closed 0, orphaned 0, synrecv 44800, timewait 0)
# ^^^^^^^^^^^^^^
# 44,800 half-open connections = SYN flood in progress
# Watch connection states
$ netstat -s | grep -i syn
12345 SYNs to LISTEN sockets received
42 times the listen queue of a socket overflowed
44800 SYNs to LISTEN sockets dropped
# Test with hping3 (authorized testing only!)
$ hping3 -S --flood -p 80 --rand-source target.example.com
# -S: SYN flag
# --flood: send as fast as possible
# --rand-source: randomize source IP (simulates real attack)
Do SYN cookies have any downsides? A small one: TCP options negotiated during the handshake (like window scaling, SACK, and timestamps) are partially lost because the server did not store them. Modern implementations encode a few bits of critical options into the cookie, but it is not perfect. That is why most systems only activate SYN cookies when the backlog queue is under pressure — during normal operation, they use regular handshakes with full option negotiation.
Application-Layer Attacks: Death by a Thousand Requests
Application-layer DDoS is the most insidious category because the traffic looks legitimate. Each request is a valid HTTP request that completes the TCP handshake, passes firewall rules, and consumes disproportionate server resources.
Slowloris: The Low-Bandwidth Killer
Slowloris is an elegant attack that holds connections open by sending HTTP headers very slowly, never completing the request. The server keeps the connection alive waiting for the rest of the headers, eventually exhausting its connection pool.
sequenceDiagram
participant Attacker as Slowloris Attacker
participant Server as Web Server<br/>(Apache: 256 max connections)
Note over Attacker: Opens 256 connections<br/>to server (one per thread)
rect rgb(255, 200, 200)
Attacker->>Server: GET / HTTP/1.1\r\n
Note over Server: Connection 1 allocated<br/>Waiting for headers...
Attacker->>Server: Host: example.com\r\n
Note over Attacker: Wait 10 seconds...
Attacker->>Server: X-Custom-1: keep-alive\r\n
Note over Attacker: Wait 10 seconds...
Attacker->>Server: X-Custom-2: keep-alive\r\n
Note over Attacker: NEVER sends final \r\n<br/>Connection stays open INDEFINITELY
end
Note over Server: After 256 connections:<br/>ALL threads occupied by slow requests<br/>No threads available for legitimate users<br/>Server appears "down" but CPU is idle
Note over Attacker: Attack bandwidth:<br/>< 1 Kbps total!<br/>Sends just enough bytes<br/>to keep connections alive
# Detect Slowloris: look for many connections from the same IP
# with very low data transfer rates
$ ss -tn state established | awk '{print $5}' | cut -d: -f1 | \
sort | uniq -c | sort -rn | head
500 203.0.113.50 # 500 connections from one IP = Slowloris
3 192.0.2.10 # Normal user
2 192.0.2.11 # Normal user
# Mitigation in nginx (naturally resistant due to event-driven architecture):
# Limit connections per IP
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 10;
# Set aggressive timeouts for header reading
client_header_timeout 5s; # Default is 60s — way too long
client_body_timeout 5s;
# Apache mitigation: use mod_reqtimeout
RequestReadTimeout header=5-10,MinRate=500 body=10,MinRate=500
# Or switch to nginx/event-driven server that handles
# connections asynchronously (one thread serves thousands of connections)
Slowloris is particularly effective against Apache because Apache allocates a full thread per connection. If the attacker opens 1,000 slow connections, Apache runs out of threads. Nginx and other event-driven servers are more resistant because they handle connections asynchronously — but even they can be overwhelmed at scale.
HTTP Flood
# HTTP flood targets expensive endpoints with legitimate-looking requests
# Detection: identify unusual request rates by IP and endpoint
$ awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
42031 203.0.113.50 # 42K requests from one IP = flood
38947 203.0.113.51
37892 198.51.100.30
245 192.0.2.10 # Normal user
128 192.0.2.11
# Identify targeted endpoints
$ awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
85420 /api/search # Search is CPU-intensive
12340 /api/reports/generate # Report generation is expensive
2341 /api/users
890 /
# Defense: rate limiting in nginx
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/search {
limit_req zone=api burst=20 nodelay;
# burst=20: allow up to 20 requests to queue
# nodelay: process burst immediately, don't spread
proxy_pass http://backend;
}
}
HTTP/2 Rapid Reset (CVE-2023-44487)
**The HTTP/2 Rapid Reset attack (CVE-2023-44487)** discovered in 2023 was a paradigm shift in application-layer DDoS. HTTP/2 allows multiplexing many streams over a single TCP connection. The attacker opens a stream and immediately sends an RST_STREAM frame to cancel it.
**Why it's devastating:**
- The server begins processing the request before the reset arrives
- The client has already moved on to the next stream
- Over a single TCP connection, millions of open-reset cycles per second are possible
- Google observed an attack peaking at **398 million requests per second** — 7.5x larger than any previously recorded Layer 7 attack
**The math:**
- Traditional HTTP flood: 1 request per TCP connection × connection setup overhead = limited
- HTTP/2 Rapid Reset: 1,000+ streams per TCP connection × instant reset = massive amplification
**Affected servers:** nginx, Apache, Envoy, HAProxy, and virtually every HTTP/2 implementation needed patches.
**The fix:** Rate-limit stream resets per connection. If a client opens and resets more than N streams per second, close the connection entirely.
Other Application-Layer Attack Variants
| Attack | Mechanism | Impact |
|---|---|---|
| R-U-Dead-Yet (RUDY) | POST with large Content-Length, sends body 1 byte at a time | Exhausts connection threads |
| HashDoS | POST data with keys that collide in hash table, turning O(1) to O(n) | Minutes of CPU from a few KB |
| ReDoS | Input triggering catastrophic backtracking in regex | One request hangs a thread for minutes |
| XML Bomb (Billion Laughs) | Recursive entity expansion: 1KB XML expands to 1GB+ in memory | Out-of-memory crash |
The Mirai Botnet: When IoT Became a Weapon
In September 2016, the Mirai botnet launched the largest DDoS attacks the world had ever seen. Its targets included security journalist Brian Krebs (620 Gbps), French hosting company OVH (1 Tbps), and DNS provider Dyn.
Mirai was unprecedented because of its simplicity and its source material: **Internet of Things devices**. Security cameras, DVRs, routers, and baby monitors — devices with default credentials that their owners never changed.
graph TD
subgraph MiraiArch["Mirai Botnet Architecture"]
Scanner["Scanning Module"]
BruteForce["Brute Force Module<br/>(62 default passwords)"]
Loader["Loader Server<br/>Delivers Mirai binary"]
CnC["Command & Control<br/>Server"]
Report["Report Server<br/>(tracks infections)"]
end
subgraph Victims["Compromised IoT Devices (600,000+)"]
Cam1["IP Camera<br/>admin/admin"]
Cam2["DVR<br/>root/root"]
Router["Home Router<br/>root/xc3511"]
DVR["NVR<br/>admin/password"]
More["... 600,000 more"]
end
subgraph Attack["DDoS Attack"]
Target["Target<br/>(Dyn DNS)"]
end
Scanner -->|"Scan port 23/22<br/>random IPs"| BruteForce
BruteForce -->|"Try 62 default<br/>credentials"| Cam1
BruteForce --> Cam2
BruteForce --> Router
BruteForce --> DVR
Cam1 --> Loader
Cam2 --> Loader
Router --> Loader
DVR --> Loader
Loader -->|"Download & execute<br/>Mirai binary"| More
More --> Report
Report --> CnC
CnC -->|"Attack command"| Cam1
CnC --> Cam2
CnC --> Router
CnC --> DVR
CnC --> More
Cam1 --> Target
Cam2 --> Target
Router --> Target
DVR --> Target
More --> Target
style Target fill:#cc2222,color:#fff
Mirai's operation was devastatingly simple:
- Scanning: Bots scanned random IPs for open Telnet (port 23) or SSH (port 22)
- Brute-forcing: Tried just 62 common default username/password combinations:
- admin/admin, root/root, root/xc3511, admin/password, root/888888...
- Infection: Logged in, downloaded the Mirai binary, killed competing malware
- Reporting: Reported the new bot to the report server
- Attack: On command from C2, all bots simultaneously flooded the target
The Dyn Attack (October 21, 2016)
This was the attack that made the front page of every newspaper. Mirai targeted Dyn, a major DNS infrastructure provider. The result was catastrophic:
graph TD
subgraph DynAttack["Dyn DNS Attack Timeline"]
T1["7:10 AM EDT<br/>First attack wave begins"]
T2["7:30 AM<br/>Dyn DNS resolution<br/>starts failing"]
T3["8:00 AM<br/>Twitter, Reddit, Netflix,<br/>GitHub, Airbnb, CNN<br/>become unreachable"]
T4["9:30 AM<br/>First wave mitigated"]
T5["11:52 AM<br/>Second attack wave"]
T6["4:00 PM<br/>Third attack wave"]
T7["6:00 PM<br/>Attack subsides"]
end
T1 --> T2 --> T3 --> T4 --> T5 --> T6 --> T7
subgraph Impact["Services Affected"]
Twitter
Netflix
Reddit
GitHub
Airbnb
CNN
Spotify
More2["+ dozens more"]
end
T3 --- Impact
Lesson["LESSON: All these services were<br/>independently healthy. Their DNS<br/>provider was the single point of<br/>failure. When Dyn went down,<br/>browsers couldn't resolve domain<br/>names to IP addresses."]
style T3 fill:#cc2222,color:#fff
Sixty-two default passwords. That is all it took to build a 600,000-device botnet. The manufacturers shipped devices with default credentials, the users never changed them, and the devices had no auto-update mechanism. Six hundred thousand devices weaponized by a 21-year-old Rutgers student who was trying to take down Minecraft servers. The collateral damage included half the US internet.
DDoS Mitigation Architecture
Anycast Routing
graph TD
subgraph Without["WITHOUT Anycast"]
Bot1A["Bot 1"] --> Single["Target Server<br/>(single location)"]
Bot2A["Bot 2"] --> Single
Bot3A["Bot 3"] --> Single
Note1["All traffic hits one server.<br/>Overwhelmed instantly."]
end
subgraph With["WITH Anycast"]
Bot1B["Bot 1<br/>(US East)"] --> POP1["POP NYC<br/>IP: X.X.X.X"]
Bot2B["Bot 2<br/>(Europe)"] --> POP2["POP London<br/>IP: X.X.X.X<br/>(same IP!)"]
Bot3B["Bot 3<br/>(Asia)"] --> POP3["POP Tokyo<br/>IP: X.X.X.X<br/>(same IP!)"]
Note2["Same IP announced from 200+<br/>locations worldwide. BGP routes<br/>each bot to nearest POP.<br/>Attack distributed geographically."]
end
style Single fill:#cc2222,color:#fff
style POP1 fill:#228844,color:#fff
style POP2 fill:#228844,color:#fff
style POP3 fill:#228844,color:#fff
Scrubbing Centers
flowchart TD
Internet["Internet Traffic<br/>(mix of legitimate + attack)"]
subgraph Scrub["DDoS Scrubbing Center"]
Analyze["Traffic Analysis Engine"]
IPRep["IP Reputation Check<br/>(known botnets, proxies)"]
Rate["Rate Pattern Analysis<br/>(too many from one source?)"]
Proto["Protocol Anomaly Check<br/>(malformed packets, flags)"]
Behavior["Behavioral Analysis<br/>(bots vs humans)"]
Challenge["Challenge-Response<br/>(JS challenge, CAPTCHA)"]
end
Internet --> Analyze
Analyze --> IPRep
Analyze --> Rate
Analyze --> Proto
Analyze --> Behavior
Analyze --> Challenge
Clean["Clean Traffic<br/>(forwarded to origin)"]
Dropped["Attack Traffic<br/>(dropped)"]
IPRep --> Clean
IPRep --> Dropped
Rate --> Clean
Rate --> Dropped
Proto --> Clean
Proto --> Dropped
Behavior --> Clean
Behavior --> Dropped
Challenge --> Clean
Challenge --> Dropped
Clean --> Origin["Origin Server<br/>(receives only<br/>legitimate traffic)"]
style Dropped fill:#cc2222,color:#fff
style Clean fill:#228844,color:#fff
Rate Limiting at Every Layer
# Layer 4: iptables rate limiting
# Limit new TCP connections to 25 per second per source IP
$ iptables -A INPUT -p tcp --syn -m connlimit --connlimit-above 25 \
--connlimit-mask 32 -j DROP
# Limit ICMP to prevent ping flood
$ iptables -A INPUT -p icmp --icmp-type echo-request \
-m limit --limit 1/s --limit-burst 4 -j ACCEPT
$ iptables -A INPUT -p icmp --icmp-type echo-request -j DROP
# Layer 7: nginx rate limiting
# Define rate limit zone: 10 requests per second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# Apply with burst allowance
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429; # Return 429 Too Many Requests
proxy_pass http://backend;
}
# Stricter limits for expensive endpoints
location /api/search {
limit_req zone=api burst=5 nodelay;
proxy_pass http://backend;
}
# No rate limit on health checks
location /health {
proxy_pass http://backend;
}
}
# HAProxy rate limiting with stick tables
frontend http_front
stick-table type ip size 100k expire 30s store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 100 }
CDN-Based Protection
graph TD
subgraph CDN["CDN Edge Network (200+ POPs worldwide)"]
L34["Layer 3/4 Protection"]
L34a["Anycast absorbs volumetric"]
L34b["SYN flood protection at edge"]
L34c["UDP amplification dropped"]
L7["Layer 7 Protection"]
L7a["WAF rules"]
L7b["Bot detection (JS challenge)"]
L7c["Rate limiting per URL/IP"]
L7d["ML anomaly detection"]
L7e["Managed rulesets (auto-updated)"]
Origin["Origin Protection"]
Origina["Origin IP hidden behind CDN"]
Originb["Only CDN IPs allowed to reach origin"]
Originc["Origin shield: one CDN node caches"]
end
Attack["DDoS Attack<br/>(Tbps)"] --> CDN
CDN -->|"Clean traffic only<br/>(Mbps)"| Server["Origin Server"]
Services["Key Services:<br/>Cloudflare: Free basic DDoS protection<br/>AWS Shield Standard: Free with all AWS<br/>AWS Shield Advanced: $3K/mo + DRT<br/>Akamai Prolexic: Enterprise scrubbing<br/>Google Cloud Armor: GCP DDoS + WAF"]
style Attack fill:#cc2222,color:#fff
style Server fill:#228844,color:#fff
DDoS Testing: Authorized Approaches
# Using hping3 for SYN flood testing (authorized environments only!)
$ hping3 -S --flood -p 80 --rand-source test-target.internal
# -S: SYN flag set
# --flood: maximum rate
# --rand-source: random source IPs
# Monitor with ss -s on the target
# Using ab (Apache Bench) for HTTP flood simulation
$ ab -n 10000 -c 100 http://test-target.internal/api/search?q=test
# -n 10000: total requests
# -c 100: concurrent connections
# Using wrk for more realistic load
$ wrk -t12 -c400 -d30s http://test-target.internal/api/search
# -t12: 12 threads
# -c400: 400 connections
# -d30s: 30 second duration
# Monitor during test:
# On target server:
$ watch -n 1 'ss -s; echo "---"; netstat -s | grep -i syn'
# Check nginx rate limiting is working:
$ grep "limiting" /var/log/nginx/error.log | wc -l
$ grep "429" /var/log/nginx/access.log | wc -l
Even "testing" a DDoS against a target you don't own is illegal. Authorized DDoS testing requires:
- Written permission from the target organization
- Notification to the hosting provider and ISP
- Coordination with DDoS mitigation providers
- Carefully scoped test parameters (duration, volume, type)
- An immediate stop mechanism
Cloud providers have specific policies:
- **AWS:** Requires "Simulated Events" form submission
- **GCP:** Requires notification through support
- **Azure:** Requires notification and scoped testing agreement
Testing without authorization can result in account termination and criminal prosecution.
DDoS Preparation Checklist
DDoS mitigation is not something you configure during an attack. You prepare in advance or you suffer. Here is what to have in place before the attack comes.
flowchart TD
subgraph Infra["INFRASTRUCTURE"]
I1["CDN/DDoS mitigation service active and tested"]
I2["Origin server IP hidden (not in DNS, not in headers)"]
I3["Anycast DNS with multiple providers"]
I4["Auto-scaling with reasonable cost limits"]
I5["SYN cookies enabled on all servers"]
I6["Rate limiting at LB, API gateway, and app"]
end
subgraph Monitor["MONITORING"]
M1["Baseline traffic patterns documented"]
M2["Alerts for traffic spikes > 3x baseline"]
M3["Alerts for connection count spikes"]
M4["Alerts for 5xx error rate spikes"]
M5["Geographic traffic distribution monitored"]
end
subgraph Runbook["RUNBOOKS"]
R1["DDoS response procedure documented"]
R2["Mitigation provider contact info ready"]
R3["Escalation path defined"]
R4["Communication templates prepared"]
R5["Under-attack mode defined (graceful degradation)"]
end
subgraph Test["TESTING"]
T1["DDoS simulation conducted annually"]
T2["Failover to mitigation tested"]
T3["Team has practiced the runbook"]
T4["BCP38 egress filtering prevents your<br/>network from being an attack source"]
end
What about just over-provisioning? Can you simply have more bandwidth than the attacker? That worked ten years ago when attacks were single-digit Gbps. Today, amplification attacks regularly reach hundreds of Gbps and even Tbps. No individual organization can out-bandwidth a determined DDoS. That is why the mitigation industry exists — companies like Cloudflare, Akamai, and AWS have aggregate network capacity of hundreds of Tbps spread across hundreds of POPs worldwide. That is the only way to absorb modern volumetric attacks.
Build a DDoS-resilient architecture for a test web application:
1. **SYN cookies:** On a Linux VM, enable `tcp_syncookies`, then use `hping3` to generate a SYN flood from another VM. Monitor with `ss -s` and verify the server handles it gracefully.
2. **Rate limiting:** Configure nginx rate limits on an API endpoint. Use `ab` to generate load. Observe 429 responses in the access log and verify legitimate requests still succeed.
3. **Slowloris simulation:** Use the `slowhttptest` tool against a test Apache server. Then switch to nginx and verify the attack is far less effective due to event-driven architecture.
4. **Connection monitoring:** During each test, capture traffic with `tcpdump` and analyze in Wireshark. Learn what SYN floods, HTTP floods, and Slowloris look like on the wire.
5. **iptables defense:** Configure `connlimit` rules to restrict connections per IP. Test with multiple parallel connections and verify the limit works.
6. **Monitoring setup:** Configure Prometheus + Grafana to visualize connection counts, request rates, and error rates. Create alerts that would trigger during the attacks above.
What You've Learned
In this chapter, we explored the full landscape of denial of service attacks and defenses:
-
DDoS attacks target availability and come in three categories: volumetric (bandwidth saturation), protocol (state exhaustion), and application-layer (resource exhaustion). Each requires different mitigation.
-
UDP amplification exploits protocols like DNS (28-54x), NTP (556x), and memcached (51,000x) where a small spoofed request generates a vastly larger response directed at the victim. The 2018 GitHub attack reached 1.35 Tbps using memcached amplification.
-
SYN floods exhaust server connection state by sending floods of SYN packets with spoofed source IPs. SYN cookies defend against this by encoding connection state in the SYN-ACK sequence number, consuming zero server resources until the handshake completes.
-
Application-layer attacks (Slowloris, HTTP floods, HTTP/2 Rapid Reset) use legitimate-looking requests to exhaust server resources. They're harder to detect because the traffic appears normal. The HTTP/2 Rapid Reset attack reached 398 million requests per second.
-
The Mirai botnet demonstrated that 600,000 IoT devices with default credentials could be weaponized by trying just 62 common passwords, creating a DDoS army that took down major DNS infrastructure.
-
The Dyn attack showed that DNS infrastructure is a single point of failure — when Dyn went down, Twitter, Netflix, Reddit, GitHub, and dozens of other independently healthy services became unreachable.
-
Mitigation requires multiple layers: anycast routing to distribute traffic geographically, scrubbing centers to filter malicious packets, CDN-based protection for both Layer 3/4 and Layer 7, rate limiting at every layer, and SYN cookies for protocol-level defense.
-
No organization can out-bandwidth a modern DDoS. The mitigation industry exists because only aggregate network capacity (hundreds of Tbps across CDN providers) can absorb volumetric attacks at scale.
-
Preparation is everything. DDoS mitigation must be configured, tested, and practiced before the attack comes. The time to write runbooks and test failover procedures is not during a 42 Gbps flood at 8:47 AM on a Monday.