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 →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact