Denial Of Service in Chi
How Denial Of Service Manifests in Chi
Denial of Service (DoS) attacks in Chi applications typically exploit the framework's async nature and resource management patterns. Chi's lightweight middleware stack and router design create specific attack vectors that attackers can leverage to exhaust system resources.
The most common DoS pattern in Chi applications involves recursive middleware execution. Since Chi middleware chains are executed sequentially, an attacker can craft requests that trigger infinite loops through conditional middleware that never resolves. For example, a middleware that checks authentication tokens but fails to properly handle malformed tokens can cause repeated execution attempts.
func vulnerableMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Missing token validation
if r.Header.Get("Authorization") == "" {
// No error response, just continues
}
next.ServeHTTP(w, r)
})
}
Another Chi-specific DoS vector targets the context cancellation patterns. Chi uses Go's context package for request-scoped values and cancellation signals. Attackers can exploit poorly implemented context handling to create goroutine leaks. When a request handler spawns background goroutines without proper context cancellation, these goroutines continue running even after the client disconnects, gradually consuming memory and CPU.
func leakyHandler(w http.ResponseWriter, r *http.Request) {
// Background goroutine without context cancellation
go func() {
for {
// Infinite loop, never checks context
time.Sleep(1 * time.Second)
log.Println("Background task running")
}
}()
w.Write([]byte("Processing"))
}
Chi's route matching algorithm also presents DoS opportunities. The framework uses a radix tree for efficient route matching, but complex route patterns with many parameters can cause excessive CPU usage during matching. Attackers can craft URLs with deeply nested parameters or unusual encoding that forces the router to perform expensive matching operations.
Resource exhaustion through file upload handling is another critical Chi DoS vector. Since Chi doesn't impose default limits on request bodies, attackers can upload extremely large files to exhaust disk space or memory. Without proper size limits and streaming processing, a single request can consume all available resources.
func unsafeUploadHandler(w http.ResponseWriter, r *http.Request) {
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
// Reads entire file into memory without limits
data, err := ioutil.ReadAll(file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Process data without size validation
processData(data)
w.Write([]byte("Upload complete"))
}
Chi-Specific Detection
Detecting DoS vulnerabilities in Chi applications requires understanding the framework's execution model and common failure patterns. The first step is runtime monitoring of request processing patterns and resource consumption.
Middleware-based monitoring provides the most effective detection approach for Chi applications. By instrumenting the middleware chain, you can track request execution time, memory allocation, and goroutine counts per request. This allows identification of requests that consume excessive resources or take abnormally long to process.
func monitoringMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
ctx := context.WithValue(r.Context(), "start_time", start)
// Capture initial goroutine count
initialGoroutines := runtime.NumGoroutine()
next.ServeHTTP(w, r.WithContext(ctx))
// Calculate metrics
duration := time.Since(start)
currentGoroutines := runtime.NumGoroutine()
goroutineDelta := currentGoroutines - initialGoroutines
log.Printf("Request: %s %s - Duration: %v - Goroutines: %d",
r.Method, r.URL.Path, duration, goroutineDelta)
// Alert on suspicious patterns
if duration > 5*time.Second || goroutineDelta > 10 {
log.Printf("Suspicious request detected: %v", r.URL)
}
})
}
Static analysis of Chi route definitions can reveal potential DoS vulnerabilities. Tools that analyze Go source code can identify patterns like unbounded request body reading, missing context cancellation, and recursive middleware calls. Look for middleware chains that don't properly handle error conditions or have circular dependencies.
Automated scanning with middleBrick provides comprehensive DoS vulnerability detection for Chi applications. The scanner analyzes both the running application and OpenAPI specifications to identify security weaknesses. For Chi specifically, middleBrick tests for:
- Missing rate limiting on endpoints
- Unbounded request body sizes
- Slowloris-style partial request attacks
- Recursive middleware execution paths
- Context cancellation bypass opportunities
middleBrick's black-box scanning approach is particularly effective for Chi applications since it tests the actual running API without requiring source code access. The scanner sends crafted requests designed to trigger DoS conditions and monitors the application's response patterns.
Performance profiling during normal operation establishes baseline metrics for comparison. Monitor CPU usage, memory allocation, and goroutine counts during typical load patterns. Sudden deviations from these baselines often indicate DoS attacks in progress.
Chi-Specific Remediation
Remediating DoS vulnerabilities in Chi applications requires a multi-layered approach that addresses both the framework's specific characteristics and general DoS prevention strategies. The most effective remediation combines rate limiting, request validation, and resource management.
Implementing rate limiting at the Chi router level provides the first line of defense. Chi's middleware architecture makes it easy to add rate limiting that applies to all routes. The key is choosing appropriate rate limits based on your application's capacity and user patterns.
func rateLimitMiddleware(next http.Handler) http.Handler {
limiter := tollbooth.NewLimiter(10, time.Minute)
limiter.SetMessage("Rate limit exceeded")
limiter.SetStatusCode(http.StatusTooManyRequests)
return tollbooth.LimitHandler(limiter, next)
}
// Apply to all routes
r := chi.NewRouter()
r.Use(rateLimitMiddleware)
r.Get("/api/users", getUsersHandler)
r.Post("/api/users", createUserHandler)
Request size limiting is critical for preventing resource exhaustion attacks. Chi doesn't impose default limits, so you must explicitly configure maximum request sizes for different content types. This prevents attackers from uploading massive files or sending oversized JSON payloads.
func sizeLimitMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set maximum request body size (10MB)
r.Body = http.MaxBytesReader(w, r.Body, 10*1024*1024)
// Check content type and apply specific limits
contentType := r.Header.Get("Content-Type")
switch {
case strings.HasPrefix(contentType, "application/json"):
r.Body = http.MaxBytesReader(w, r.Body, 2*1024*1024) // 2MB for JSON
case strings.HasPrefix(contentType, "multipart/form-data"):
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024) // 50MB for uploads
}
next.ServeHTTP(w, r)
})
}
Context cancellation patterns must be properly implemented to prevent goroutine leaks. Every background goroutine spawned by a request handler should respect the request context and terminate when the client disconnects or the timeout expires.
func safeHandler(w http.ResponseWriter, r *http.Request) {
// Set a reasonable timeout for the request
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
// Background goroutine with context cancellation
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
log.Println("Background task cancelled")
return
default:
// Do work
time.Sleep(1 * time.Second)
}
}
}(ctx)
// Main request processing
select {
case <-ctx.Done():
http.Error(w, "Request timeout", http.StatusGatewayTimeout)
default:
w.Write([]byte("Request processed successfully"))
}
}
Middleware execution safety requires careful design to prevent infinite loops and recursive calls. Each middleware should have clear exit conditions and proper error handling. Use middleware composition patterns that guarantee termination.
func safeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check for recursive call prevention
if r.Context().Value("middleware_depth") != nil {
http.Error(w, "Recursive middleware detected", http.StatusInternalServerError)
return
}
// Set middleware depth for this call
ctx := context.WithValue(r.Context(), "middleware_depth", true)
// Call next with timeout
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Connection pooling and timeout configuration at the server level prevents resource exhaustion. Configure appropriate timeouts for read, write, and idle connections to ensure that stalled connections don't consume resources indefinitely.
server := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20, // 1MB
}
Monitoring and alerting systems should track key metrics like request duration, memory usage, and goroutine counts. Set up alerts for abnormal patterns that might indicate DoS attacks in progress.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |