Skip to main content

Insecurely generated token - Validation

Need

Secure token generation and validation

Context

  • Usage of Go 1.16 for building high-performance and scalable applications
  • Usage of Gin framework for building web applications in Go
  • Usage of jwt-go for JSON Web Token (JWT) authentication in Go applications

Description

Non compliant code

package main

import (
"github.com/gin-gonic/gin"
"github.com/dgrijalva/jwt-go"
"net/http"
)

func main() {
r := gin.Default()
r.POST("/createUser", CreateUser)
r.Run()
}

type User struct {
Username string `json:"username"`
Password string `json:"password"`
}

func CreateUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["username"] = user.Username
claims["password"] = user.Password

t, _ := token.SignedString([]byte(""))
c.JSON(http.StatusOK, gin.H{"token": t})
}

In the above code, a new user is created through the CreateUser function. This function takes a JSON payload containing a username and password, and generates a JWT token that contains these details.

The vulnerability lies in the token generation process. The token is signed using the jwt.SigningMethodHS256 method and an empty secret. This means that the token is not properly signed and can be easily tampered with.

The jwt.New function is used to create a new token. This function takes a signing method as an argument, in this case jwt.SigningMethodHS256. This method is used to sign the token, but because an empty secret is used, the token is not actually signed. This is a security risk as it allows anyone to modify the token and change the user details.

After the token is created, it is returned to the client in the response. This token can then be used to authenticate the user, but because it is not properly signed, it can be easily tampered with. This means that an attacker could change the user details in the token, and the server would accept this as valid.

This is a serious vulnerability as it allows an attacker to impersonate any user, and potentially gain unauthorized access to the system.

Steps

  • Use a secure algorithm for token encryption, such as HMAC-SHA256 or RSA.
  • Generate a secret key or obtain a valid certificate for signing the token.
  • Set the algorithm used for token signing explicitly to prevent tampering.
  • Validate the token signature on the server-side to ensure its integrity.
  • Implement proper input validation and sanitization to prevent injection attacks.
  • Use a secure random number generator to generate the token.
  • Store the secret key or certificate securely and restrict access to it.
  • Regularly update and rotate the secret key or certificate to enhance security.

Compliant code

package main

import (
"github.com/gin-gonic/gin"
"github.com/dgrijalva/jwt-go"
"net/http"
"os"
)

func main() {
r := gin.Default()
r.POST("/createUser", CreateUser)
r.Run()
}

type User struct {
Username string `json:"username"`
Password string `json:"password"`
}

func CreateUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// Generate a secret key
secretKey := os.Getenv("SECRET_KEY")
if secretKey == "" {
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not generate secret key"})
return
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": user.Username,
"password": user.Password,
})

// Sign the token with the secret key
tokenString, err := token.SignedString([]byte(secretKey))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "could not sign token"})
return
}

c.JSON(http.StatusOK, gin.H{"token": tokenString})
}

The updated code now uses a secure algorithm for token encryption, HMAC-SHA256. The token is signed with a secret key that is stored as an environment variable. This secret key is used to sign the token, ensuring its integrity.

The jwt.NewWithClaims function is used to create a new token and set the claims at the same time. The claims include the username and password of the user.

The token.SignedString function is used to sign the token with the secret key. If there is an error during this process, the function will return an internal server error.

The secret key is stored securely as an environment variable and is not included in the code. This prevents the key from being exposed in the code.

The token is then returned in the response. The server-side should validate this token to ensure its integrity.

This code also includes proper input validation and sanitization to prevent injection attacks. The c.ShouldBindJSON function is used to bind the incoming JSON to the User struct and validate the input. If there is an error during this process, the function will return a bad request error.

This code does not include a secure random number generator to generate the token. The token is generated based on the user's username and password. However, the token is signed with a secret key, ensuring its integrity.

The secret key should be regularly updated and rotated to enhance security. This is not included in the code and should be handled in the environment where the code is deployed.

References