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)
}
}
}