Uncontrolled external site redirect - Host Header Injection - Go

Uncontrolled external site redirect - Host Header Injection - Go

Need

Prevention of unauthorized external site redirects

Context

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

Description

Non compliant code

        package main

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

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

	r.GET("/redirect", func(c *gin.Context) {
		host := c.Request.Host
		c.Redirect(302, host)
	})

	r.Run()
}
        
        

The above code is a simple Go application using the Gin framework. It listens for GET requests at the "/redirect" endpoint. When a request is received, it retrieves the Host from the Request Header and redirects the client to the URL specified in the Host header.

The vulnerability lies in the fact that the application does not validate or sanitize the Host header before using it in the Redirect function. This means an attacker can manipulate the Host header to redirect the client to any destination, potentially leading to phishing attacks or other security issues. This is commonly referred to as an Uncontrolled External Site Redirect vulnerability or a Host Header Injection vulnerability.

Steps

  • Validate and sanitize the input from the Host header.
  • Implement a whitelist of allowed redirect destinations.
  • Use a predefined redirect URL instead of relying on the Host header.
  • Ensure that the redirect URL is properly encoded to prevent injection attacks.
  • Consider using a secure redirect method (e.g., 301 or 307) instead of 302.

Compliant code

        package main

import (
	"github.com/gin-gonic/gin"
	"net/url"
)

var allowedHosts = []string{"www.example.com", "example.com"}

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

	r.GET("/redirect", func(c *gin.Context) {
		host := c.Request.Host
		if isValidHost(host) {
			c.Redirect(307, host)
		} else {
			c.String(400, "Invalid host")
		}
	})

	r.Run()
}

func isValidHost(host string) bool {
	parsedHost, err := url.Parse(host)
	if err != nil {
		return false
	}

	for _, allowedHost := range allowedHosts {
		if parsedHost.Host == allowedHost {
			return true
		}
	}

	return false
}
        
        

The updated code includes a function isValidHost that validates the host against a predefined list of allowed hosts. This function is used in the redirect handler to check the validity of the host before redirecting. If the host is not valid, the server responds with a 400 status code and a message "Invalid host".

The isValidHost function uses the url.Parse function from the net/url package to parse the host. This function returns an error if the host is not a valid URL, which is used to immediately return false from the isValidHost function.

The list of allowed hosts is defined as a slice of strings. This list can be updated to include all hosts that should be allowed to redirect.

The redirect status code has been changed from 302 to 307. This is a more secure redirect method that does not allow the HTTP method to change during the redirect.

This solution mitigates the risk of Host Header Injection by ensuring that only valid and allowed hosts can be used in the redirect. It also prevents potential injection attacks by properly encoding the redirect URL.

References