logo

Insecure functionality - Pass the hash - Go


Need

Secure authentication and authorization mechanisms


Context

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

Description

Insecure Code Example

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

  1. Replace the direct comparison of the password hash with a secure password verification mechanism, such as bcrypt or Argon2.
  2. Store the hashed passwords securely in a database or other storage mechanism.
  3. 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.
  4. Implement proper password salting to further enhance the security of the password storage.
  5. Consider implementing additional security measures, such as multi-factor authentication, to protect against Pass The Hash attacks.

Secure Code Example

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

  • 255 - Insecure functionality - Pass the hash

  • Last updated

    2023/09/18