Skip to main content

Insecure functionality - Pass the hash

Need

Secure authentication and authorization mechanisms

Context

  • Usage of Go 1.16 for building efficient and scalable applications
  • Usage of gin-gonic/gin for building web applications and APIs in Go
  • Usage of net/http for creating HTTP servers in a Node.js application

Description

Non compliant code

package main

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

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

r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
passwordHash := c.PostForm("passwordHash")

// This is the insecure part, it directly compares the hash of the password
// instead of the actual password. This allows for Pass The Hash attacks.
if isValidUser(username, passwordHash) {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
})

r.Run()
}

// Dummy function for user validation
func isValidUser(username string, passwordHash string) bool {
// In a real-world app, this function would check the username and password hash against a database
// For the sake of this example, it simply returns true if the username is "admin" and the password hash matches a predefined hash
return username == "admin" && passwordHash == "5f4dcc3b5aa765d61d8327deb882cf99"
}

The code above represents a simple login endpoint in a Go web application using the Gin framework. The vulnerability lies in the /login endpoint where it accepts a username and passwordHash as POST parameters.

The isValidUser function then checks if the username and passwordHash are valid. If they are, it responds with a status of "you are logged in", otherwise, it responds with "unauthorized".

The vulnerability here is in the use of password hashes for authentication. This is insecure because it exposes the system to Pass The Hash attacks. In a Pass The Hash attack, an attacker who has somehow obtained a user's password hash can send that hash to the system to authenticate themselves as that user. This bypasses the need for the attacker to know the user's actual password.

In this code, if an attacker were to somehow obtain the hash of the password for the 'admin' user (which is hardcoded in this example as "5f4dcc3b5aa765d61d8327deb882cf99"), they could authenticate themselves as the 'admin' user by sending a POST request to the /login endpoint with username=admin and passwordHash=5f4dcc3b5aa765d61d8327deb882cf99.

To fix this vulnerability, the system should not use password hashes for authentication. Instead, it should require the user's actual password, which it then hashes and compares to the stored hash for that user.

Steps

  • Replace the direct comparison of the password hash with a secure password verification mechanism, such as bcrypt or Argon2.
  • Store the hashed passwords securely in a database or other storage mechanism.
  • When a user logs in, retrieve the hashed password from the storage and use the secure password verification mechanism to compare it with the provided password.
  • Implement proper password salting to further enhance the security of the password storage.
  • Consider implementing additional security measures, such as multi-factor authentication, to protect against Pass The Hash attacks.

Compliant code

package main

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

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

r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

// Securely compare the password with the hashed password stored in the database
if isValidUser(username, password) {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
})

r.Run()
}

// Dummy function for user validation
func isValidUser(username string, password string) bool {
// In a real-world app, this function would check the username and password against a database
// For the sake of this example, it simply returns true if the username is "admin" and the password is correct
hashedPassword := "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy" // bcrypt hash of "password"
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return username == "admin" && err == nil
}

The original code was vulnerable to Pass The Hash attacks because it directly compared the hash of the password instead of the actual password. This allowed an attacker to authenticate as a user if they could obtain the hash of the user's password.

The fixed code mitigates this vulnerability by using the bcrypt library to securely hash and verify passwords. When a user logs in, the provided password is hashed and compared to the stored hashed password using bcrypt's CompareHashAndPassword function. This function securely compares the hashes, preventing Pass The Hash attacks.

In a real-world application, the hashed passwords would be securely stored in a database or other secure storage mechanism. When a user logs in, the application would retrieve the hashed password from the storage and use it in the CompareHashAndPassword function.

Additionally, bcrypt automatically handles password salting, which further enhances the security of the password storage. Salting is a technique where a random value is added to the password before hashing, which prevents precomputed hash attacks.

Finally, it's recommended to implement additional security measures, such as multi-factor authentication, to further protect against Pass The Hash and other authentication-related attacks.

References