Skip to main content

User enumeration

Need

Prevention of user enumeration

Context

  • Usage of Go 1.16 for building high-performance and scalable applications
  • Usage of gin-gonic/gin for building web applications in Go
  • Usage of net/http for handling HTTP requests and responses in a Node.js server

Description

Non compliant code

package main

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

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.POST("/login", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

for _, u := range Users {
if u.Username == user.Username {
if u.Password == user.Password {
c.JSON(http.StatusOK, gin.H{"status": "Logged in"})
return
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "Wrong password"})
return
}
}
}

c.JSON(http.StatusNotFound, gin.H{"status": "User not found"})
})

r.Run()
}

The above code is a simple Go server using the Gin framework, simulating a login process. It has an array of users, each with a username and password. When a POST request is made to the '/login' endpoint with a JSON body containing a username and password, the server checks if the user exists and if the password is correct.

The vulnerability lies in the fact that the server responds differently depending on whether the user exists and whether the password is correct. If the username does not exist in the Users array, the server responds with a 404 status code and a "User not found" message. If the username exists but the password is incorrect, the server responds with a 401 status code and a "Wrong password" message. This allows an attacker to enumerate valid users by observing the server's responses.

For instance, if the attacker sends a request with a username that they are not sure exists, and the response is "User not found", they know that the username is not valid. If the response is "Wrong password", they know that the username is valid and can then start trying to guess the password.

This type of vulnerability is a form of user enumeration, which can lead to further attacks such as brute-force attacks.

Steps

  • Implement a consistent error response for both existent and non-existent users.
  • Avoid providing specific error messages that reveal whether the user exists or not.
  • Use a constant response time for all login attempts to prevent timing attacks.
  • Implement rate limiting or CAPTCHA to prevent brute force attacks.
  • Consider using a secure password storage mechanism, such as bcrypt, to protect user passwords.
  • Regularly review and update the code to address any new vulnerabilities that may arise.

Compliant code

package main

import (
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
)

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

var Users = []User{
{Username: "user1", Password: "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcA/3Qz3hC5bDjq8s9tcRfWxE7."},
{Username: "user2", Password: "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcA/3Qz3hC5bDjq8s9tcRfWxE7."},
}

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

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

for _, u := range Users {
if u.Username == user.Username {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password))
if err == nil {
c.JSON(http.StatusOK, gin.H{"status": "Logged in"})
return
}
}
}

time.Sleep(1 * time.Second) // constant response time
c.JSON(http.StatusUnauthorized, gin.H{"status": "Invalid credentials"})
})

r.Run()
}

The updated code addresses the user enumeration vulnerability by implementing a consistent error response for both existent and non-existent users.

  1. The error message returned for invalid login attempts is now always "Invalid credentials", regardless of whether the username exists or not. This prevents an attacker from being able to determine if a user exists based on the error message.

  2. The code now uses the bcrypt library to securely store and compare passwords. This protects user passwords even if an attacker is able to gain access to the user data.

  3. A constant response time of 1 second is implemented for all login attempts. This prevents timing attacks where an attacker could determine if a user exists based on the response time.

  4. Although not shown in the code, it is recommended to implement rate limiting or CAPTCHA to prevent brute force attacks. This could be done using middleware in the gin framework.

  5. Regular code reviews and updates should be performed to address any new vulnerabilities that may arise.

References