Prevention of unauthorized account lockouts
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
var loginAttempts = make(map[string]int)
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
if loginAttempts[username] >= 5 {
c.JSON(http.StatusForbidden, gin.H{"error": "This account has been locked."})
return
}
// Assume checkLogin is a function that checks if the username and password are correct
if checkLogin(username, password) {
c.JSON(http.StatusOK, gin.H{"message": "Login successful"})
} else {
loginAttempts[username]++
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
}
})
r.Run()
}
func checkLogin(username, password string) bool {
// This is just a placeholder. In a real app, you would check the username and password against your database here.
return username == "admin" && password == "password"
}
The above Go code is an example of a web server using the Gin framework, which is vulnerable to account lockout. The vulnerability lies in the
/login
endpoint.
When a POST request is made to the
/login
endpoint, the server checks if the username and password provided in the form data are correct. If they are correct, a success message is sent back to the client. However, if they are not correct, the server increments a count of failed login attempts for that username.
The problem arises when the count of failed login attempts for a username reaches 5. At this point, the server will respond to any further login attempts with an error message saying that the account has been locked, regardless of whether the correct password is provided.
This means that an attacker could intentionally make 5 failed login attempts for a user, causing that user's account to be locked and preventing the user from being able to login, even with the correct password. This is known as an account lockout vulnerability.
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"time"
)
const maxLoginAttempts = 5
const lockoutDuration = 10 * time.Minute
type User struct {
Username string
LoginAttempts int
LastFailedLogin time.Time
}
var users = make(map[string]*User)
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
user, exists := users[username]
if !exists {
user = &User{Username: username}
users[username] = user
}
if user.LoginAttempts >= maxLoginAttempts && time.Now().Sub(user.LastFailedLogin) < lockoutDuration {
c.JSON(http.StatusForbidden, gin.H{"error": "This account has been locked."})
return
}
// Assume checkLogin is a function that checks if the username and password are correct
if checkLogin(username, password) {
user.LoginAttempts = 0
c.JSON(http.StatusOK, gin.H{"message": "Login successful"})
} else {
user.LoginAttempts++
user.LastFailedLogin = time.Now()
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
}
})
r.Run()
}
func checkLogin(username, password string) bool {
// This is just a placeholder. In a real app, you would check the username and password against your database here.
return username == "admin" && password == "password"
}
The updated code introduces a lockout mechanism to prevent account lockouts due to excessive login attempts.
A new
User
struct is introduced to keep track of each user's login attempts and the time of their last failed login. The
users
map is updated to store pointers to
User
structs instead of just the login attempt count.
In the login handler, we first check if the user exists in the
users
map. If not, we create a new
User
struct for them and add it to the map.
Before checking the login credentials, we now also check if the user has exceeded the maximum number of login attempts (
maxLoginAttempts
) and if the lockout duration (
lockoutDuration
) has not yet passed since their last failed login. If both conditions are met, we return a "This account has been locked" error and do not proceed with the login attempt.
If the login credentials are correct, we reset the user's login attempt count to 0. If the credentials are incorrect, we increment the login attempt count and update the time of the last failed login.
This way, users are only locked out of their account if they exceed the maximum number of login attempts within the lockout duration. After the lockout duration has passed, they can attempt to login again.