Asymmetric denial of service - Go

Asymmetric denial of service - Go

Need

The solution to the vulnerability of asymmetric denial of service is the need for robust resource management and mitigation techniques to prevent service outages caused by amplification attacks or resource-consuming malicious requests.

Context

  • Usage of Go 1.13 for building high-performance and concurrent applications
  • Usage of gin-gonic/gin for building web applications in Go

Description

Non compliant code

        package main

import (
	"github.com/gin-gonic/gin"
	"time"
)

func main() {
	router := gin.Default()

	router.GET("/ping", func(c *gin.Context) {
		time.Sleep(10 * time.Second) // Simulates an expensive operation
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})

	router.Run(":8080")
}
        
        

In the code snippet above, we have a simple server using the gin framework in Go. The server has a single route, /ping, which responds with a JSON object containing the message "pong".

The vulnerability here lies in the time.Sleep(10 * time.Second) line. This simulates a resource-intensive operation that takes a significant amount of time to complete. In a real-world scenario, this could represent a complex database query, a computationally expensive operation, or a call to an external service that takes a long time to respond.

If an attacker were to send a large number of requests to the /ping route, each request would cause the server to pause for 10 seconds. This could quickly consume all available resources, causing the server to become unresponsive. This is a form of a Denial of Service (DoS) attack, and in this case, it's asymmetric because a small number of requests from the attacker can consume a large amount of resources on the server.

This code is vulnerable because it does not limit the number of concurrent requests that can be handled or the amount of resources that a single request can consume.

Steps

  • Implement rate limiting to prevent amplification attacks.
  • Use timeouts to limit the amount of time a request can consume resources.
  • Consider using a separate worker pool to handle expensive operations.
  • Optimize the expensive operation to reduce its execution time.
  • Monitor server performance and resource usage to detect and mitigate potential DoS attacks.

Compliant code

        package main

import (
	"github.com/gin-gonic/gin"
	"github.com/didip/tollbooth"
	"github.com/didip/tollbooth_gin"
	"time"
	"context"
)

func main() {
	router := gin.Default()

	// Implement rate limiting
	limiter := tollbooth.NewLimiter(1, nil) // limit to 1 request per second per IP

	router.GET("/ping", tollbooth_gin.LimitHandler(limiter), func(c *gin.Context) {
		// Use a context with timeout to prevent long running requests
		ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
		defer cancel()

		// Simulate an expensive operation in a separate goroutine
		done := make(chan bool)
		go func() {
			time.Sleep(10 * time.Second) // This should be replaced with the actual operation
			done <- true
		}()

		// Wait for the operation to complete or the timeout to expire
		select {
		case <-done:
			c.JSON(200, gin.H{
				"message": "pong",
			})
		case <-ctx.Done():
			c.JSON(408, gin.H{
				"message": "Request took too long",
			})
		}
	})

	router.Run(":8080")
}
        
        

The updated code includes several measures to mitigate the risk of an asymmetric denial of service attack:

1. Rate Limiting: We use the tollbooth library to limit the rate at which requests can be made to the server. This prevents an attacker from overwhelming the server with a large number of requests in a short period of time.

2. Timeouts: We use a context with a timeout to limit the amount of time a request can consume resources. If the operation takes longer than the specified timeout, the request is cancelled and a 408 (Request Timeout) response is returned to the client.

3. Separate Goroutine for Expensive Operation: The expensive operation is performed in a separate goroutine. This allows the main goroutine to continue processing other requests while the operation is being performed.

4. Handling Long Running Requests: If the expensive operation takes longer than the specified timeout, the request is cancelled and a 408 (Request Timeout) response is returned to the client. This prevents a single malicious request from consuming an enormous amount of resources and rendering the server unresponsive.

These measures should significantly reduce the risk of an asymmetric denial of service attack. However, it's important to continue monitoring server performance and resource usage to detect and mitigate potential DoS attacks.

References