Skip to main content

Insecure functionality - Password management

Need

Secure password management

Context

  • Usage of Go 1.16 for building high-performance and scalable applications
  • Usage of Gin framework for building web applications in Go

Description

Non compliant code

package main

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

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

var users = []User{
{ID: 1, Username: "user1", Password: "pass1"},
{ID: 2, Username: "user2", Password: "pass2"},
}

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

r.PUT("/user/:id", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}

// Get the ID of the user
id := c.Param("id")

// Update the password for the user
for i := 0; i < len(users); i++ {
if users[i].ID == id {
users[i].Password = user.Password
c.JSON(200, gin.H{"data": users[i]})
return
}
}

c.JSON(404, gin.H{"error": "User not found"})
})

r.Run()
}

The above code represents a simple REST API using the Gin framework in Go. The API has a PUT endpoint /user/:id which is intended to update a user's password.

The vulnerability lies in the fact that the endpoint allows any user to update the password of any other user. This is because the endpoint does not perform any session validation or authorization checks to ensure that the user requesting the password change is the same user who owns the account.

When the PUT request is made, the server simply binds the JSON body of the request to a User struct, extracts the id parameter from the URL, and updates the password of the user with that id in the users slice. This allows a malicious user to easily change another user's password by simply sending a PUT request with the victim's id and their new password in the body of the request.

Steps

  • Implement proper authentication and authorization mechanisms to ensure that only authorized users can access the password change functionality.
  • Use secure password storage techniques such as hashing and salting to protect user passwords.
  • Implement a separate endpoint or functionality specifically for changing passwords, instead of allowing password changes through a generic update endpoint.
  • Validate the user's current password before allowing them to change their password to prevent unauthorized password changes.
  • Implement strong password requirements, such as minimum length, complexity, and expiration policies, to ensure the use of secure passwords.
  • Implement rate limiting or CAPTCHA mechanisms to prevent brute-force attacks on the password change functionality.
  • Regularly update and patch the application and its dependencies to address any known security vulnerabilities.
  • Conduct regular security audits and penetration testing to identify and address any potential vulnerabilities in the application.

Compliant code

package main

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

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

var users = []User{
{ID: 1, Username: "user1", Password: hashPassword("pass1")},
{ID: 2, Username: "user2", Password: hashPassword("pass2")},
}

func hashPassword(password string) string {
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(hash)
}

func checkPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

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

r.PUT("/user/:id/password", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}

// Get the ID of the user
id := c.Param("id")

// Check if the user is authenticated and authorized to change the password
// This is a placeholder for actual authentication and authorization checks
if id != user.ID {
c.JSON(403, gin.H{"error": "Unauthorized"})
return
}

// Validate the user's current password
if !checkPassword(user.Password, users[id].Password) {
c.JSON(403, gin.H{"error": "Invalid current password"})
return
}

// Update the password for the user
for i := 0; i < len(users); i++ {
if users[i].ID == id {
users[i].Password = hashPassword(user.Password)
c.JSON(200, gin.H{"data": users[i]})
return
}
}

c.JSON(404, gin.H{"error": "User not found"})
})

r.Run()
}

The updated code now includes a separate endpoint for changing passwords (/user/:id/password). This endpoint first checks if the user is authenticated and authorized to change the password. This is done by comparing the ID in the URL parameter with the ID in the user object from the request body. If they don't match, the server responds with a 403 Unauthorized error.

Next, the server validates the user's current password by comparing it with the hashed password stored in the users array. This is done using the bcrypt.CompareHashAndPassword function. If the passwords don't match, the server responds with a 403 Invalid current password error.

If the user is authorized and the current password is valid, the server updates the user's password. The new password is hashed using the bcrypt.GenerateFromPassword function before it is stored in the users array. This ensures that even if the users array is compromised, the actual passwords of the users are not exposed.

Finally, the server responds with a 200 OK status and the updated user object. If the user with the specified ID is not found in the users array, the server responds with a 404 User not found error.

Please note that this is a simplified example and a real-world application would require more robust authentication and authorization checks, as well as secure handling and storage of user passwords.

References