DDoS #
Distributed Denial of Service (DDoS) adalah serangan yang bertujuan membuat layanan tidak tersedia bagi pengguna yang sah — bukan dengan mengeksploitasi kelemahan logika atau mencuri data, tapi dengan cara yang lebih langsung: membanjiri sistem dengan traffic sampai resource habis dan tidak bisa melayani permintaan yang legitimate.
Yang membuat DDoS sulit dihadapi adalah sifatnya yang asimetris. Attacker mengirimkan jutaan request dengan biaya yang sangat rendah — menggunakan botnet, amplification technique, atau cloud instance murah — sementara korban harus menyediakan resource yang cukup untuk melayani setiap request, termasuk yang palsu. Membangun kapasitas yang cukup untuk menahan serangan skala besar seringkali tidak ekonomis. Pendekatan yang lebih realistis adalah kombinasi dari deteksi dini, filtering, rate limiting, dan bergantung pada infrastruktur yang dirancang untuk skala.
Tapi tidak semua “DDoS” berasal dari attacker. Traffic spike yang tiba-tiba dari viral content, produk yang tiba-tiba populer, atau bot yang tidak dikontrol bisa memberikan efek yang sama: sistem tidak tersedia. Mitigasi DDoS yang baik sekaligus melindungi dari semua skenario ini.
Tiga Kategori DDoS #
Memahami kategori serangan membantu menentukan mitigasi yang tepat — berbeda kategori, berbeda cara menghadapinya.
graph TD
A[DDoS Attack] --> B[Volumetric]
A --> C[Protocol]
A --> D["Application Layer\nLayer 7"]
B --> B1["UDP Flood\nICMP Flood\nDNS Amplification\nNTP Amplification"]
C --> C1["SYN Flood\nPing of Death\nSmurf Attack"]
D --> D1["HTTP Flood\nSlowloris\nRudy Attack\nAPI Abuse"]
B1 --> E["Saturasi Bandwidth\nGbps - Tbps"]
C1 --> F["Saturasi Connection Table\nOS/Network Level"]
D1 --> G["Exhausts Server Resources\nCPU/Memory/DB Connection"]Volumetric Attack #
Saturasi bandwidth — mengirimkan traffic dalam volume yang melampaui kapasitas jaringan korban. Ini yang sering muncul di berita: “serangan DDoS 1 Tbps.”
DNS Amplification Attack — contoh volumetric:
Teknik: attacker mengirim query DNS dengan source IP yang dipalsukan
(source IP = IP korban)
1. Attacker kirim query ke ribuan DNS resolver publik:
Source IP: victim.com (dipalsukan)
Query: ANY example.com (query yang menghasilkan response besar)
2. DNS resolver merespons ke korban (bukan ke attacker):
Query: ~40 bytes
Response: ~3000 bytes → amplification factor 75x!
3. Ribuan DNS resolver serentak membanjiri korban
dengan traffic yang mereka tidak minta
4. Bandwidth korban tersaturasi
1000 resolver × 75x amplification = 75.000x dari traffic attacker
Cara mitigasi:
→ ISP/CDN: anycast + scrubbing center
→ DNS: rate limit responses, Response Rate Limiting (RRL)
→ Tidak bisa di-handle di level aplikasi — butuh infrastruktur
Protocol Attack #
Mengeksploitasi kelemahan dalam protocol jaringan untuk menghabiskan resource di level network device atau OS.
SYN Flood:
TCP 3-way handshake normal:
Client → Server: SYN
Server → Client: SYN-ACK
Client → Server: ACK ← koneksi established
SYN Flood attack:
Attacker → Server: SYN (dengan source IP dipalsukan)
Server → ? : SYN-ACK (tidak bisa sampai — IP palsu)
Server tunggu ACK... (30-120 detik)
Attacker kirim jutaan SYN → server menyimpan jutaan half-open connection
Connection table penuh → tidak bisa menerima koneksi baru
Server tidak bisa melayani pengguna legitimate
Mitigasi:
→ SYN cookies: server tidak menyimpan state sampai ACK diterima
→ Firewall/Load balancer: limit half-open connections per IP
→ OS tuning: net.ipv4.tcp_syncookies = 1
Application Layer Attack (Layer 7) #
Serangan yang mensimulasikan request legitimate — sulit dibedakan dari traffic nyata, tidak membutuhkan bandwidth besar.
// Contoh Slowloris — satu mesin bisa membuat server tidak responsif
// Attacker mengirim HTTP header perlahan-lahan, tidak pernah menyelesaikan request
// Simulasi konseptual (jangan digunakan untuk serangan):
// import "net"
// sockets := make([]net.Conn, 0, 200)
// for i := 0; i < 200; i++ {
// s, _ := net.Dial("tcp", target+":80")
// s.Write([]byte("GET / HTTP/1.1\r\n"))
// s.Write([]byte("Host: target.com\r\n"))
// sockets = append(sockets, s)
// }
//
// for {
// for _, s := range sockets {
// s.Write([]byte("X-a: b\r\n")) // kirim header satu per satu, sangat lambat
// }
// time.Sleep(15 * time.Second)
// }
// Server menyimpan koneksi ini tetap terbuka karena header belum selesai
// 200 koneksi bisa mematikan server Apache dengan MaxClients kecil
// Mitigasi Slowloris:
// → Nginx lebih tahan dari Apache (event-driven, bukan thread-per-connection)
// → Request timeout yang ketat
// → Limit koneksi per IP
Rate Limiting: Lapisan Pertahanan Pertama di Aplikasi #
Rate limiting membatasi jumlah request yang bisa dilakukan satu client dalam periode waktu tertentu. Ini tidak menghentikan DDoS skala besar, tapi sangat efektif untuk membatasi dampak dan melindungi dari API abuse dan scraping.
// Implementasi rate limiting berlapis dengan Redis
// redisClient adalah client go-redis (import "github.com/redis/go-redis/v9")
var redisClient = redis.NewClient(&redis.Options{Addr: "redis:6379"})
var ctx = context.Background()
// RateLimiter menggunakan sliding window algorithm.
// Lebih akurat dari fixed window — tidak ada spike di batas window.
type RateLimiter struct {
Redis *redis.Client
MaxRequests int
WindowSeconds int
}
type RateLimitResult struct {
Allowed bool
Remaining int
ResetAt int64
}
func (rl *RateLimiter) IsAllowed(identifier string) RateLimitResult {
now := time.Now().Unix()
key := "rate:" + identifier
// Hapus entries di luar window, hitung, tambah, set expiry — satu pipeline
pipe := rl.Redis.TxPipeline()
pipe.ZRemRangeByScore(ctx, key, "0", strconv.FormatInt(now-int64(rl.WindowSeconds), 10))
card := pipe.ZCard(ctx, key)
pipe.ZAdd(ctx, key, redis.Z{Score: float64(now), Member: now})
pipe.Expire(ctx, key, time.Duration(rl.WindowSeconds)*time.Second)
_, _ = pipe.Exec(ctx)
currentCount := int(card.Val())
return RateLimitResult{
Allowed: currentCount < rl.MaxRequests,
Remaining: max(0, rl.MaxRequests-currentCount-1),
ResetAt: now + int64(rl.WindowSeconds),
}
}
// Beberapa limiter dengan threshold berbeda untuk endpoint berbeda
// Global: semua endpoint
var globalLimiter = &RateLimiter{Redis: redisClient, MaxRequests: 1000, WindowSeconds: 60}
// API endpoint sensitif: lebih ketat
var apiLimiter = &RateLimiter{Redis: redisClient, MaxRequests: 100, WindowSeconds: 60}
// Auth endpoint: sangat ketat untuk mencegah brute force
var authLimiter = &RateLimiter{Redis: redisClient, MaxRequests: 10, WindowSeconds: 300}
// Wrapper pengganti dekorator Python.
// identifierFunc menentukan identitas client (IP, user, atau kombinasi).
func rateLimit(limiter *RateLimiter, identifierFunc func() string, handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Identifier: bisa per IP, per user, atau kombinasi
identifier := r.RemoteAddr
if identifierFunc != nil {
identifier = identifierFunc()
}
result := limiter.IsAllowed(identifier)
if !result.Allowed {
writeJSON(w, http.StatusTooManyRequests, map[string]string{
"error": "Rate limit exceeded",
"message": "Terlalu banyak request. Coba lagi nanti.",
})
return
}
// Tambahkan rate limit info ke response header
w.Header().Set("X-RateLimit-Limit", strconv.Itoa(limiter.MaxRequests))
w.Header().Set("X-RateLimit-Remaining", strconv.Itoa(result.Remaining))
w.Header().Set("X-RateLimit-Reset", strconv.FormatInt(result.ResetAt, 10))
handler(w, r)
}
}
// Penggunaan:
// http.HandleFunc("/api/data", rateLimit(apiLimiter, nil, getData))
// http.HandleFunc("/login", rateLimit(authLimiter, nil, login))
// Rate limit per user (authenticated):
func getUserIdentifier() string {
if currentUserIsAuthenticated() {
return "user:" + currentUserID()
}
return "ip:" + requestRemoteAddr()
}
// http.HandleFunc("/api/expensive-operation", rateLimit(apiLimiter, getUserIdentifier, expensiveOperation))
Circuit Breaker untuk Melindungi Dependency #
Ketika satu service dibanjiri request, efeknya bisa cascade ke service lain yang bergantung padanya. Circuit breaker memutus aliran request ke service yang sedang dalam tekanan untuk memberikan waktu recovery.
// Circuit breaker untuk melindungi external calls dari cascade failure.
type CircuitState int
const (
StateClosed CircuitState = iota // Normal — request diizinkan
StateOpen // Gagal — request langsung ditolak
StateHalfOpen // Testing recovery — beberapa request diizinkan
)
type CircuitBreaker struct {
failureThreshold int // gagal N kali → OPEN
recoveryTimeout time.Duration // tunggu N detik sebelum HALF_OPEN
successThreshold int // berhasil N kali di HALF_OPEN → CLOSED
mu sync.Mutex
state CircuitState
failureCount int
successCount int
lastFailureAt time.Time
}
func NewCircuitBreaker(failureThreshold, recoveryTimeoutSeconds, successThreshold int) *CircuitBreaker {
return &CircuitBreaker{
failureThreshold: failureThreshold,
recoveryTimeout: time.Duration(recoveryTimeoutSeconds) * time.Second,
successThreshold: successThreshold,
state: StateClosed,
}
}
// ErrCircuitOpen dikembalikan selama circuit terbuka.
var ErrCircuitOpen = errors.New("circuit breaker open — service tidak tersedia")
func (cb *CircuitBreaker) Call(fn func() (interface{}, error)) (interface{}, error) {
cb.mu.Lock()
if cb.state == StateOpen {
// Cek apakah sudah waktunya recovery
if time.Since(cb.lastFailureAt) > cb.recoveryTimeout {
cb.state = StateHalfOpen
cb.successCount = 0
} else {
cb.mu.Unlock()
return nil, ErrCircuitOpen
}
}
cb.mu.Unlock()
result, err := fn()
if err == nil {
cb.mu.Lock()
if cb.state == StateHalfOpen {
cb.successCount++
if cb.successCount >= cb.successThreshold {
cb.state = StateClosed
cb.failureCount = 0
}
}
cb.mu.Unlock()
return result, nil
}
cb.mu.Lock()
cb.failureCount++
cb.lastFailureAt = time.Now()
if cb.failureCount >= cb.failureThreshold {
cb.state = StateOpen
log.Printf("Circuit breaker OPEN: too many failures (count=%d)", cb.failureCount)
}
cb.mu.Unlock()
return nil, err
}
// Penggunaan:
var (
dbCircuit = NewCircuitBreaker(5, 30, 2)
paymentCircuit = NewCircuitBreaker(3, 60, 2)
)
// user, err := dbCircuit.Call(func() (interface{}, error) { return userRepo.FindByID(userID) })
// payment, err := paymentCircuit.Call(func() (interface{}, error) { return paymentGateway.Charge(amount, card) })
Caching sebagai DDoS Shield #
Cache yang agresif mengurangi beban ke backend secara drastis — bahkan jika ada DDoS, banyak request bisa dilayani dari cache tanpa menyentuh database.
// Cache response endpoint secara agresif di Redis untuk mengurangi beban backend.
// redisClient adalah client go-redis (import "github.com/redis/go-redis/v9")
var redisClient = redis.NewClient(&redis.Options{Addr: "redis:6379"})
var ctx = context.Background()
// CacheResponse meng-cache response endpoint.
// varyOn berisi daftar query parameter yang mempengaruhi cache key.
func CacheResponse(ttl time.Duration, varyOn ...string) func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Build cache key dari endpoint + parameter
keyParts := []string{r.URL.Path}
if len(varyOn) > 0 {
for _, param := range varyOn {
value := r.URL.Query().Get(param)
keyParts = append(keyParts, param+"="+value)
}
} else {
// Default: vary on semua query params
query := r.URL.Query()
sortedParams := make([]string, 0, len(query))
for k, v := range query {
sortedParams = append(sortedParams, k+"="+v[0])
}
sort.Strings(sortedParams)
keyParts = append(keyParts, sortedParams...)
}
sum := md5.Sum([]byte(strings.Join(keyParts, "|")))
cacheKey := "cache:" + hex.EncodeToString(sum[:])
// Cek cache
cached, err := redisClient.Get(ctx, cacheKey).Result()
if err == nil {
w.Header().Set("X-Cache", "HIT")
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(cached))
return
}
// Cache miss — jalankan handler lewat recorder
rec := httptest.NewRecorder()
next(rec, r)
for k, v := range rec.Header() {
w.Header()[k] = v
}
w.Header().Set("X-Cache", "MISS")
w.WriteHeader(rec.Code)
// Simpan ke cache jika response OK
if rec.Code == http.StatusOK {
_ = redisClient.Set(ctx, cacheKey, rec.Body.String(), ttl).Err()
}
_, _ = w.Write(rec.Body.Bytes())
}
}
}
// Endpoint publik yang sering diakses — cache agresif
// http.HandleFunc("/products", CacheResponse(5*time.Minute, "category", "page")(listProducts)) // cache 5 menit
// Endpoint statis — cache sangat lama
// http.HandleFunc("/api/config", CacheResponse(time.Hour)(getConfig)) // cache 1 jam
Strategi caching untuk DDoS resistance:
Tingkat 1 — CDN cache (paling efektif untuk volumetric):
→ Static assets (JS, CSS, images) di-serve dari CDN edge
→ Attacker membanjiri CDN, bukan origin server
→ CDN punya kapasitas jauh lebih besar dari server biasa
Tingkat 2 — Application cache (Redis/Memcached):
→ Response API yang tidak berubah sering di-cache
→ Mengurangi beban ke database secara signifikan
→ Bahkan di bawah serangan, cached response bisa dilayani
Tingkat 3 — Database query cache:
→ Query yang sama tidak dieksekusi berulang
→ Connection pool yang efisien
Trade-off: data bisa stale
→ Tentukan TTL berdasarkan seberapa cepat data perlu fresh
→ Gunakan cache invalidation untuk data yang harus selalu fresh
Deteksi Anomali Traffic #
Tidak semua traffic spike adalah DDoS. Tapi spike yang tidak terduga dan tidak proporsional dengan pola normal adalah indikasi yang perlu diinvestigasi.
// Monitor traffic dan deteksi anomali.
type TrafficMonitor struct {
windowSize int
thresholdMultiplier float64
requestCounts []requestCount // (timestamp, count)
baselineRPS float64 // requests per second baseline
}
type requestCount struct {
at time.Time
count int
}
func NewTrafficMonitor(windowSize int, thresholdMultiplier float64) *TrafficMonitor {
return &TrafficMonitor{windowSize: windowSize, thresholdMultiplier: thresholdMultiplier}
}
func (m *TrafficMonitor) RecordRequests(count int) {
now := time.Now()
m.requestCounts = append(m.requestCounts, requestCount{at: now, count: count})
// Hapus data di luar window
cutoff := now.Add(-time.Duration(m.windowSize) * time.Second)
i := 0
for i < len(m.requestCounts) && m.requestCounts[i].at.Before(cutoff) {
i++
}
m.requestCounts = m.requestCounts[i:]
}
func (m *TrafficMonitor) CurrentRPS() float64 {
if len(m.requestCounts) == 0 {
return 0
}
total := 0
for _, rc := range m.requestCounts {
total += rc.count
}
return float64(total) / float64(m.windowSize)
}
func (m *TrafficMonitor) IsAnomalous() bool {
current := m.CurrentRPS()
if m.baselineRPS == 0 {
return false
}
// Anomali jika traffic melebihi N kali baseline
return current > m.baselineRPS*m.thresholdMultiplier
}
func (m *TrafficMonitor) UpdateBaseline(rps float64) {
if m.baselineRPS == 0 {
m.baselineRPS = rps
} else {
// Exponential moving average
m.baselineRPS = 0.9*m.baselineRPS + 0.1*rps
}
}
// Deteksi berdasarkan distribusi IP
type IPAnomalyDetector struct {
window int
ipCounts map[string][]time.Time
}
func NewIPAnomalyDetector(windowSeconds int) *IPAnomalyDetector {
return &IPAnomalyDetector{window: windowSeconds, ipCounts: map[string][]time.Time{}}
}
func (d *IPAnomalyDetector) RecordRequest(ip string) {
now := time.Now()
d.ipCounts[ip] = append(d.ipCounts[ip], now)
// Hapus data lama
cutoff := now.Add(-time.Duration(d.window) * time.Second)
times := d.ipCounts[ip]
i := 0
for i < len(times) && times[i].Before(cutoff) {
i++
}
d.ipCounts[ip] = times[i:]
}
type SuspiciousIP struct {
IP string
Count int
Rate float64
}
// Return IP dengan lebih dari threshold request dalam window.
func (d *IPAnomalyDetector) SuspiciousIPs(threshold int) []SuspiciousIP {
var suspicious []SuspiciousIP
for ip, times := range d.ipCounts {
if len(times) > threshold {
suspicious = append(suspicious, SuspiciousIP{
IP: ip,
Count: len(times),
Rate: float64(len(times)) / float64(d.window),
})
}
}
sort.Slice(suspicious, func(a, b int) bool {
return suspicious[a].Count > suspicious[b].Count
})
return suspicious
}
// Middleware untuk monitoring
var (
monitor = NewTrafficMonitor(60, 3.0)
ipDetector = NewIPAnomalyDetector(60)
)
func monitorTraffic(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
monitor.RecordRequests(1)
ipDetector.RecordRequest(ip)
// Cek anomali setiap N request untuk menghindari overhead
if monitor.IsAnomalous() {
log.Printf("Traffic anomaly detected: current=%.2f rps baseline=%.2f rps",
monitor.CurrentRPS(), monitor.baselineRPS)
}
// Block IP yang sangat agresif (bisa juga di nginx level)
suspicious := ipDetector.SuspiciousIPs(200)
for _, s := range suspicious {
if s.IP == ip {
writeJSON(w, http.StatusTooManyRequests, map[string]string{
"error": "Too many requests from your IP",
})
return
}
}
next(w, r)
}
}
Konfigurasi Infrastruktur untuk DDoS Resistance #
Sebagian besar mitigasi DDoS yang efektif tidak terjadi di level aplikasi — ia terjadi di level infrastruktur.
# Nginx — konfigurasi untuk DDoS resistance
# Rate limiting di level nginx
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
server {
listen 443 ssl;
# Batasi koneksi per IP
limit_conn conn_per_ip 20;
# Timeout yang ketat — mencegah Slowloris
client_body_timeout 10s;
client_header_timeout 10s;
keepalive_timeout 30s;
send_timeout 10s;
# Batasi ukuran request body
client_max_body_size 10m;
# Return 444 (close connection tanpa response) untuk user agent mencurigakan
if ($http_user_agent ~* (bot|crawler|spider|scraper|scan)) {
return 444;
}
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
# Proxy ke aplikasi
proxy_pass http://app:8000;
proxy_read_timeout 30s;
proxy_connect_timeout 10s;
}
location /login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://app:8000;
}
# Cache static assets di CDN/browser
location /static/ {
expires 7d;
add_header Cache-Control "public, immutable";
}
}
# CDN dan DDoS protection (Cloudflare sebagai contoh)
# Konfigurasi via Terraform atau dashboard
# Prinsip konfigurasi:
# 1. Always-on DDoS protection: traffic selalu melalui CDN/scrubbing center
# 2. Challenge suspicious traffic: CAPTCHA atau JS challenge untuk IP mencurigakan
# 3. IP reputation blocking: block IP yang diketahui berbahaya
# 4. Rate limiting di edge: sebelum traffic sampai ke origin server
# 5. Geo-blocking: jika traffic dari country tertentu tidak relevan secara bisnis
# Konfigurasi rate limit di CDN (konseptual):
# - /api/*: 1000 request/menit per IP
# - /login: 10 request/menit per IP
# - /register: 5 request/menit per IP
# - Static assets: tidak di-limit (dilayani dari cache)
Application-Level DDoS: Resource Exhaustion #
Attacker tidak selalu perlu volume besar. Mereka bisa target endpoint yang mengkonsumsi resource besar dengan sedikit request.
// Endpoint yang rentan resource exhaustion:
func searchVulnerable(w http.ResponseWriter, r *http.Request) {
// Query yang kompleks tanpa batas
query := r.URL.Query().Get("q")
results := productRepo.Search(query) // full table scan pada tabel besar
writeJSON(w, http.StatusOK, results)
}
// Jika attacker kirim 50 request bersamaan ke endpoint ini → database overload
// BENAR: endpoint yang resource-aware
func searchSafe(w http.ResponseWriter, r *http.Request) {
query := strings.TrimSpace(r.URL.Query().Get("q"))
// Validasi minimum panjang query
if len(query) < 3 {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Query minimal 3 karakter"})
return
}
// Batasi panjang query
if len(query) > 100 {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Query terlalu panjang"})
return
}
// Gunakan full-text index, bukan LIKE %...%
// Limit hasil
results := productRepo.SearchFullText(query, 50)
writeJSON(w, http.StatusOK, results)
}
// Operasi yang expensive harus di-queue, bukan diproses synchronous
func generateReport(w http.ResponseWriter, r *http.Request) {
// Tidak proses langsung — masukkan ke queue
job := reportQueue.Enqueue(GenerateReportTask{
UserID: currentUserID(r),
Payload: r.Body,
JobTimeout: 5 * time.Minute, // maksimum 5 menit
})
writeJSON(w, http.StatusAccepted, map[string]string{ // 202 Accepted
"job_id": job.ID,
"status": "queued",
"check_url": "/reports/status/" + job.ID,
})
}
// Registrasi (menggunakan net/http):
// http.HandleFunc("/search", rateLimit(apiLimiter, nil, searchSafe))
// http.HandleFunc("/reports/generate", rateLimit(authLimiter, nil, generateReport))
Anti-Pattern yang Harus Dihindari #
// ✗ Anti-pattern 1: tidak ada rate limiting sama sekali
// Setiap endpoint bisa diakses tanpa batas
// Satu client bisa membuat ribuan request per detik
// ✗ Anti-pattern 2: rate limiting yang terlalu mudah dibypass
// Limit hanya berdasarkan IP — mudah dibypass dengan banyak IP
// atau dengan menggunakan proxy/VPN
// ✓ Solusi: kombinasi IP + user ID + fingerprinting
// ✗ Anti-pattern 3: synchronous processing untuk operasi mahal
// func exportCSV(w http.ResponseWriter, r *http.Request) {
// data := orderRepo.FindAll() // OOM risk
// generateCSV(w, data) // timeout risk
// }
// ✓ Solusi: queue ke background worker, return job ID
// ✗ Anti-pattern 4: tidak ada timeout untuk downstream dependency
// resp, err := http.Get("https://external-api.com/data") // tanpa timeout
// ✓ Solusi: selalu set timeout
// client := &http.Client{Timeout: 10 * time.Second}
// ✗ Anti-pattern 5: cache yang bisa di-abuse untuk cache poisoning
// func getProfile(w http.ResponseWriter, r *http.Request) {
// // Jika tidak validasi ownership, attacker bisa cache
// // response dari user lain dan serve ke user lain
// profile := userRepo.FindByID(userID)
// writeJSON(w, http.StatusOK, profile)
// }
// ✓ Solusi: cache key harus include authenticated user ID untuk data private
// ✗ Anti-pattern 6: tidak ada monitoring traffic
// Spike DDoS baru ketahuan setelah sistem down
// ✓ Solusi: alerting real-time untuk RPS anomaly
Checklist DDoS Mitigation #
RATE LIMITING:
□ Rate limiting aktif di semua endpoint public
□ Threshold berbeda untuk endpoint sensitif (auth, payment)
□ Rate limit headers dikirim di response (Retry-After, X-RateLimit-*)
□ Rate limiting di multiple level (nginx, application, CDN)
INFRASTRUCTURE:
□ CDN atau DDoS protection service aktif (Cloudflare, AWS Shield, dll)
□ Anycast routing untuk menyebarkan traffic ke multiple PoP
□ Auto-scaling dikonfigurasi dengan batas yang jelas
□ Bandwidth capacity sudah diperhitungkan untuk peak + buffer
NGINX/LOAD BALANCER:
□ Request timeout dikonfigurasi (mencegah Slowloris)
□ max connection per IP dibatasi
□ Request body size dibatasi
□ Rate limiting di level nginx untuk endpoint kritis
CACHING:
□ Response yang bisa di-cache sudah di-cache agresif
□ Static assets disajikan dari CDN, tidak dari origin
□ Cache TTL dikonfigurasi sesuai kebutuhan freshness
CIRCUIT BREAKER:
□ Circuit breaker aktif untuk semua external dependency
□ Fallback behavior terdefinisi jika circuit terbuka
□ Recovery timeout dikonfigurasi dengan benar
RESOURCE PROTECTION:
□ Endpoint yang resource-intensive memiliki rate limit ketat
□ Operasi mahal di-queue ke background worker
□ Timeout dikonfigurasi untuk semua downstream calls
□ Database connection pool tidak bisa di-exhaust oleh satu endpoint
MONITORING & ALERTING:
□ Alert dipasang untuk RPS yang melebihi threshold
□ Alert untuk error rate yang naik tiba-tiba
□ Alert untuk latency P99 yang naik signifikan
□ Dashboard real-time untuk traffic pattern
□ Runbook DDoS tersedia dan pernah dipraktikkan
INCIDENT RESPONSE:
□ Prosedur untuk mengaktifkan proteksi tambahan saat serangan
□ Kontak ISP/CDN untuk eskalasi tersedia
□ IP blocking bisa dilakukan cepat (nginx, CDN, firewall)
□ Communication plan untuk user jika downtime terjadi
Ringkasan #
- DDoS ada tiga kategori berbeda — volumetric (saturasi bandwidth), protocol (exhausts network state), dan application layer (exhausts server resource). Mitigasi yang tepat berbeda untuk setiap kategori.
- Mitigasi volumetric tidak bisa dilakukan di level aplikasi — butuh CDN, anycast routing, dan scrubbing center yang punya kapasitas bandwidth lebih besar dari traffic attacker. Ini domain ISP dan CDN provider.
- Rate limiting adalah pertahanan pertama di level aplikasi — sliding window lebih akurat dari fixed window. Implementasikan di multiple level: CDN, nginx, dan aplikasi.
- Application layer DDoS lebih berbahaya karena terlihat legitimate — request yang valid tapi dalam volume besar, atau request ke endpoint yang expensive, bisa membuat server down tanpa volume yang besar.
- Caching mengurangi blast radius DDoS — request yang dilayani dari cache tidak menyentuh database. Cache agresif pada endpoint publik membuat sistem lebih resilient terhadap spike traffic apapun.
- Circuit breaker mencegah cascade failure — ketika satu service down karena dibanjiri, circuit breaker memastikan request yang masuk tidak terus menunggu dan memblokir resource.
- Operasi expensive harus di-queue — report generation, bulk export, dan komputasi berat tidak boleh diproses synchronous. Attacker bisa exploit ini dengan sedikit request.
- Timeout untuk semua downstream call wajib — tanpa timeout, satu external API yang lambat bisa membuat semua thread hanging dan server effectively down.
- Monitoring real-time adalah kunci deteksi dini — alert untuk RPS anomaly, error rate spike, dan latency degradation memungkinkan respons sebelum sistem benar-benar down.
- Runbook dan incident response plan harus siap sebelum serangan terjadi — saat sedang diserang bukan waktu yang tepat untuk memikirkan apa yang harus dilakukan. Latihan regular memastikan tim bisa respons cepat dan tepat.
← Sebelumnya: Remote Code Execution Berikutnya: Brute Force →