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
Insecure Code Example
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.
Secure Code Example
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
Last updated
2023/09/18