Frontend Labs
⋯

Day 113 — End-to-End Attack Simulation

Learning objective Run 8 simultaneous attack types against the fully integrated stack; verify each is detected and mitigated
Key packages net/http, sync, context, testing
Security context Unit tests verify individual components; integration attack tests verify the system actually defends

Key Concepts

Attack playbook for integration testing:

Attack Expected detection Expected mitigation
SQL injection WAF SQLiRule score ≥ threshold 403 block, WAF audit log
XSS WAF XSSRule score ≥ threshold 403 block
Path traversal WAF PathTraversalRule Immediate 403
Credential stuffing AbuseDetector per-username limit 429 + delay
HTTP flood CIDRRateLimiter token bucket 429 Retry-After
Slowloris SlowlorisDetector byte rate Connection close
Bot scraping BotScorer (velocity + datacenter IP) PoW challenge
Honeypot probe HoneypotTracker Permanent shadow-ban

Exercise — Attack Simulator

package integration

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
    "sync/atomic"
    "testing"
    "time"
)

type AttackResult struct {
    Name      string
    Sent      int
    Blocked   int
    Allowed   int
    BlockRate float64
}

// AttackSimulator runs attack scenarios against a test server
type AttackSimulator struct {
    baseURL string
    client  *http.Client
}

func NewAttackSimulator(baseURL string) *AttackSimulator {
    return &AttackSimulator{
        baseURL: baseURL,
        client: &http.Client{
            Timeout: 5 * time.Second,
            CheckRedirect: func(*http.Request, []*http.Request) error {
                return http.ErrUseLastResponse // don't follow redirects
            },
        },
    }
}

// 1. SQL Injection
func (s *AttackSimulator) SQLInjection(t *testing.T, n int) AttackResult {
    payloads := []string{
        "' OR '1'='1",
        "'; DROP TABLE users; --",
        "1 UNION SELECT * FROM passwords",
        "admin'--",
        "' OR 1=1--",
    }
    return s.runHTTPAttack(t, "sqli", n, func(i int) *http.Request {
        payload := payloads[i%len(payloads)]
        req, _ := http.NewRequest("GET", s.baseURL+"/api/users?id="+payload, nil)
        return req
    }, http.StatusForbidden)
}

// 2. XSS
func (s *AttackSimulator) XSS(t *testing.T, n int) AttackResult {
    payloads := []string{
        "<script>alert(1)</script>",
        "<img src=x onerror=alert(1)>",
        "javascript:alert(1)",
        "<svg onload=alert(1)>",
    }
    return s.runHTTPAttack(t, "xss", n, func(i int) *http.Request {
        payload := payloads[i%len(payloads)]
        req, _ := http.NewRequest("GET", s.baseURL+"/search?q="+payload, nil)
        return req
    }, http.StatusForbidden)
}

// 3. Path Traversal
func (s *AttackSimulator) PathTraversal(t *testing.T, n int) AttackResult {
    payloads := []string{
        "../../../etc/passwd",
        "..%2F..%2F..%2Fetc%2Fpasswd",
        "%2e%2e/%2e%2e/etc/passwd",
        "....//....//etc/passwd",
    }
    return s.runHTTPAttack(t, "path_traversal", n, func(i int) *http.Request {
        req, _ := http.NewRequest("GET", s.baseURL+"/files/"+payloads[i%len(payloads)], nil)
        return req
    }, http.StatusForbidden)
}

// 4. Credential Stuffing
func (s *AttackSimulator) CredentialStuffing(t *testing.T, n int) AttackResult {
    return s.runHTTPAttack(t, "credential_stuffing", n, func(i int) *http.Request {
        body, _ := json.Marshal(map[string]string{
            "username": fmt.Sprintf("user%d@example.com", i%100), // cycle through 100 usernames
            "password": fmt.Sprintf("leaked-pass-%d", i),
        })
        req, _ := http.NewRequest("POST", s.baseURL+"/api/auth/login", bytes.NewReader(body))
        req.Header.Set("Content-Type", "application/json")
        return req
    }, http.StatusTooManyRequests)
}

// 5. HTTP Flood
func (s *AttackSimulator) HTTPFlood(t *testing.T, n int, concurrency int) AttackResult {
    var blocked, allowed atomic.Int64
    sem := make(chan struct{}, concurrency)
    var wg sync.WaitGroup

    for i := 0; i < n; i++ {
        wg.Add(1)
        sem <- struct{}{}
        go func(i int) {
            defer wg.Done()
            defer func() { <-sem }()
            req, _ := http.NewRequest("GET", s.baseURL+"/api/data", nil)
            resp, err := s.client.Do(req)
            if err != nil {
                blocked.Add(1)
                return
            }
            resp.Body.Close()
            if resp.StatusCode == http.StatusTooManyRequests {
                blocked.Add(1)
            } else {
                allowed.Add(1)
            }
        }(i)
    }
    wg.Wait()

    b, a := int(blocked.Load()), int(allowed.Load())
    return AttackResult{
        Name: "http_flood", Sent: n, Blocked: b, Allowed: a,
        BlockRate: float64(b) / float64(n),
    }
}

// 6. Bot Scraping (high velocity, datacenter UA)
func (s *AttackSimulator) BotScraping(t *testing.T, n int) AttackResult {
    return s.runHTTPAttack(t, "bot_scraping", n, func(i int) *http.Request {
        req, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/products?page=%d", s.baseURL, i), nil)
        req.Header.Set("User-Agent", "python-requests/2.28.0") // known bot UA
        // Deliberately missing Accept-Language, Accept-Encoding
        return req
    }, http.StatusTooManyRequests) // expect PoW challenge or block
}

// 7. Honeypot Probe
func (s *AttackSimulator) HoneypotProbe(t *testing.T) AttackResult {
    req, _ := http.NewRequest("GET", s.baseURL+"/api/hidden", nil)
    resp, err := s.client.Do(req)
    result := AttackResult{Name: "honeypot_probe", Sent: 1}
    if err == nil {
        resp.Body.Close()
        // Honeypot returns 200 (shadow-ban) — subsequent requests should be shadow-banned
        result.Blocked = 1
        result.BlockRate = 1.0
    }

    // Verify subsequent request from same IP gets shadow-banned
    req2, _ := http.NewRequest("GET", s.baseURL+"/api/products", nil)
    resp2, err2 := s.client.Do(req2)
    if err2 == nil {
        defer resp2.Body.Close()
        var data map[string]interface{}
        json.NewDecoder(resp2.Body).Decode(&data)
        if results, ok := data["results"]; ok {
            if arr, ok := results.([]interface{}); ok && len(arr) == 0 {
                t.Log("honeypot: subsequent request shadow-banned (empty results)")
            }
        }
    }
    return result
}

func (s *AttackSimulator) runHTTPAttack(t *testing.T, name string, n int, makeReq func(int) *http.Request, expectedBlockStatus int) AttackResult {
    var blocked, allowed atomic.Int64
    var wg sync.WaitGroup

    for i := 0; i < n; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            req := makeReq(i)
            resp, err := s.client.Do(req)
            if err != nil {
                blocked.Add(1)
                return
            }
            resp.Body.Close()
            if resp.StatusCode == expectedBlockStatus || resp.StatusCode == http.StatusForbidden {
                blocked.Add(1)
            } else {
                allowed.Add(1)
            }
        }(i)
    }
    wg.Wait()

    b, a := int(blocked.Load()), int(allowed.Load())
    result := AttackResult{Name: name, Sent: n, Blocked: b, Allowed: a, BlockRate: float64(b) / float64(n)}
    t.Logf("attack=%s sent=%d blocked=%d (%.0f%%) allowed=%d", name, n, b, result.BlockRate*100, a)
    return result
}

// RunFullPlaybook runs all attacks and verifies block rates
func RunFullPlaybook(t *testing.T, serverURL string) {
    sim := NewAttackSimulator(serverURL)

    results := []AttackResult{
        sim.SQLInjection(t, 50),
        sim.XSS(t, 50),
        sim.PathTraversal(t, 50),
        sim.CredentialStuffing(t, 200),
        sim.HTTPFlood(t, 500, 50),
        sim.BotScraping(t, 100),
        sim.HoneypotProbe(t),
    }

    // Verify attack detection rates
    for _, r := range results {
        if r.BlockRate < 0.8 {
            t.Errorf("FAIL: %s block rate %.0f%% < 80%% (%d/%d blocked)",
                r.Name, r.BlockRate*100, r.Blocked, r.Sent)
        } else {
            t.Logf("OK: %s block rate %.0f%%", r.Name, r.BlockRate*100)
        }
    }
}

Generated from week16.md · 8 Oct 2026 16:36