Automatic information enumeration - Personal Information - Go

Automatic information enumeration - Personal Information - Go

Need

Protection of user privacy and prevention of unauthorized access to personal information

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 (
    "github.com/gin-gonic/gin"
)

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

var users = []User{
    {Username: "user1", Password: "password1"},
    {Username: "user2", Password: "password2"},
}

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

    r.GET("/users", func(c *gin.Context) {
        c.JSON(200, users)
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}
        
        

In the code snippet above, we have a simple Go backend application using the Gin framework. This application has an endpoint /users that returns a list of all users when a GET request is made to it.

The vulnerability here is that this endpoint does not require any form of authorization or authentication. This means that anyone can make a GET request to this endpoint and retrieve the list of all users, including their usernames and passwords.

This is a serious security vulnerability known as automatic information enumeration. It can lead to unauthorized access to personal information and can be exploited to perform further attacks, such as identity theft or phishing.

Furthermore, the passwords are stored in plain text which is another security vulnerability. Passwords should be hashed and salted to prevent them from being exposed even if an attacker manages to retrieve the user data.

The absence of a token-based authentication system (like JWT) leaves the application open to such vulnerabilities. With a token-based system, the server could verify the identity of the client making the request and ensure they have the necessary permissions to access the user data.

Steps

  • Implement authentication and authorization mechanisms to ensure that only authenticated and authorized users can access user information.
  • Use tokens or session management to associate user requests with their respective accounts.
  • Verify the user's identity and permissions before allowing access to sensitive user information.
  • Implement access controls to restrict access to user information based on the user's role or privileges.
  • Ensure that sensitive user information is not exposed in API responses unless explicitly authorized.
  • Regularly review and update the authentication and authorization mechanisms to address any potential vulnerabilities.

Compliant code

        package main

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

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

var users = []User{
    {Username: "user1", Password: "password1"},
    {Username: "user2", Password: "password2"},
}

var jwtKey = []byte("secret")

type Claims struct {
    Username string `json:"username"`
    jwt.StandardClaims
}

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

    r.POST("/login", func(c *gin.Context) {
        var creds User
        if err := c.ShouldBindJSON(&creds); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"message": "Bad request"})
            return
        }

        for _, user := range users {
            if user.Username == creds.Username && user.Password == creds.Password {
                expirationTime := time.Now().Add(5 * time.Minute)
                claims := &Claims{
                    Username: creds.Username,
                    StandardClaims: jwt.StandardClaims{
                        ExpiresAt: expirationTime.Unix(),
                    },
                }

                token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
                tokenString, err := token.SignedString(jwtKey)

                if err != nil {
                    c.JSON(http.StatusInternalServerError, gin.H{"message": "Internal server error"})
                    return
                }

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

        c.JSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
    })

    r.GET("/users", func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        claims := &Claims{}

        token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
            return jwtKey, nil
        })

        if err != nil {
            if err == jwt.ErrSignatureInvalid {
                c.JSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
                return
            }
            c.JSON(http.StatusBadRequest, gin.H{"message": "Bad request"})
            return
        }

        if !token.Valid {
            c.JSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
            return
        }

        c.JSON(http.StatusOK, users)
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}
        
        

The updated code now includes a login route that accepts a username and password. If the credentials match a user in the users slice, a JWT token is generated and returned to the client. This token includes the username and an expiration time, and is signed with a secret key.

The /users route now requires a valid JWT token in the Authorization header of the request. The token is parsed and validated, and if it is valid, the user information is returned. If the token is not valid or not present, an Unauthorized status is returned.

This ensures that only authenticated users can access the user information, and that the user information is associated with the correct user account. The use of JWT tokens also provides a stateless method of managing user sessions, which can be beneficial in a distributed system.

Please note that this is a basic implementation and may not include all necessary error handling and security measures for a production system. For example, the secret key used to sign the tokens is hardcoded and the passwords are stored in plaintext, both of which are security risks. In a production system, you would want to store the secret key securely and hash the passwords.

References