Insecure generation of random numbers - Go

Insecure generation of random numbers - Go

Need

Secure generation of random numbers

Context

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

Description

Non compliant code

        package main

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

func main() {
	r := gin.Default()
	r.GET("/random", func(c *gin.Context) {
		rand.Seed(time.Now().UnixNano())
		result := rand.Intn(100)
		c.JSON(200, gin.H{
			"randomNumber": result,
		})
	})
	r.Run()
}
        
        

The above code is a simple Gin framework based Go application that generates a random number when the /random endpoint is hit.

The vulnerability lies in the usage of the math/rand package to generate random numbers. The rand.Seed(time.Now().UnixNano()) line is used to seed the random number generator. The seed for the random number generator is the current time in nanoseconds since 1970. This is a common but insecure way of seeding a random number generator as it is predictable, especially if the attacker knows the approximate time the numbers were generated.

Furthermore, the rand.Intn(100) function generates a pseudo-random number in the range [0,100). The range is small and the function is deterministic, meaning it will produce the same sequence of numbers for the same seed. If an attacker can guess or know the seed, they can predict the sequence of random numbers.

This vulnerability could potentially be exploited in various ways, depending on the context in which the random numbers are used. For example, if these numbers are used for session IDs, OTPs, or cryptographic keys, it could lead to session hijacking, OTP bypass, or decryption of sensitive data.

Steps

  • Replace the use of the math/rand package with a cryptographically secure random number generator.
  • Use a high-entropy source of randomness, such as the crypto/rand package in Go.
  • Seed the random number generator with a secure source of entropy, such as the current time combined with a cryptographically secure random number.
  • Ensure that the generated random numbers have a sufficient range and are not easily predictable.
  • Test the changes thoroughly to ensure the security and correctness of the random number generation.

Compliant code

        package main

import (
	"crypto/rand"
	"encoding/binary"
	"fmt"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/random", func(c *gin.Context) {
		var n int64
		binary.Read(rand.Reader, binary.BigEndian, &n)
		result := n % 100
		c.JSON(200, gin.H{
			"randomNumber": result,
		})
	})
	r.Run()
}
        
        

The original code was using the math/rand package to generate random numbers. This package is not secure for generating random numbers as it uses a pseudo-random number generator which can be predictable and hence, exploitable.

The updated code uses the crypto/rand package which is a cryptographically secure random number generator. This package reads from /dev/urandom on Unix-like systems and CryptGenRandom on Windows, both of which are considered to be secure sources of randomness.

The binary.Read(rand.Reader, binary.BigEndian, &n) line reads a random int64 from the rand.Reader which is a global, shared instance of a cryptographically secure random number generator.

The result := n % 100 line ensures that the generated random number is within the range of 0 to 99.

This change makes the random number generation in the application secure and unpredictable, thus fixing the vulnerability.

References