Insecure generation of random numbers
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.