Secure password management
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.
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.