Email uniqueness not properly verified
Need
Enforce email uniqueness verification during the registration process
Context
- Usage of Go 1.16 for building high-performance and scalable applications
- Usage of gin-gonic/gin for building web applications and APIs in Go
Description
Non compliant code
package main
import (
"github.com/gin-gonic/gin"
)
type User struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}
func main() {
r := gin.Default()
r.POST("/register", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// TODO: Add email uniqueness verification here
c.JSON(200, gin.H{"message": "Registration successful"})
})
r.Run()
}
This is a simple Go application using the Gin framework. It has a POST /register
endpoint that accepts a JSON payload with email
and password
fields to create a new user.
The User
struct is used to bind the incoming JSON payload. It has two fields, Email
and Password
, both are required.
The problem with this code is that it does not properly verify the uniqueness of the email. It allows the plus character in the email registration process. An attacker could abuse this feature to create several accounts pointing to the same email. For example, an attacker could register with [email protected]
, [email protected]
, [email protected]
, etc., and all these emails will point to the same [email protected]
email address.
To fix this vulnerability, you need to add a function that checks the uniqueness of the email address before creating a new user. This function should remove any characters after the plus sign in the local part of the email address before checking for uniqueness.
Steps
- Implement a database query to check if the email already exists in the system.
- Modify the code to perform the email uniqueness verification before registering the user.
- If the email already exists, return an appropriate error response indicating that the email is already registered.
- If the email is unique, proceed with the user registration process as usual.
Compliant code
package main
import (
"github.com/gin-gonic/gin"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
}
var db *sql.DB
func main() {
var err error
db, err = sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err)
}
r := gin.Default()
r.POST("/register", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Check if email already exists
var existingEmail string
err := db.QueryRow("SELECT email FROM users WHERE email = ?", user.Email).Scan(&existingEmail)
if err != nil && err != sql.ErrNoRows {
c.JSON(500, gin.H{"error": "Database error"})
return
}
if existingEmail != "" {
c.JSON(400, gin.H{"error": "Email already registered"})
return
}
// TODO: Add user registration code here
c.JSON(200, gin.H{"message": "Registration successful"})
})
r.Run()
}
The code above is a fixed version of the original vulnerable code. The vulnerability was that the system allowed the same email to be registered multiple times. This could be exploited by an attacker to create multiple accounts with the same email.
The fix involves adding a database query to check if the email already exists in the system before registering a new user. This is done using the QueryRow
function of the sql
package, which executes a query that is expected to return at most one row.
If the email already exists in the database, the existingEmail
variable will be assigned the existing email, and the system will return an error response indicating that the email is already registered. If the email does not exist in the database, the existingEmail
variable will remain an empty string, and the system will proceed with the user registration process.
Note that the database connection is established at the beginning of the main
function, and the db
variable is declared as a global variable so it can be accessed from the route handler function. The database connection string is a placeholder and should be replaced with your actual database connection string.
Also, the user registration code is not included in this example. You should replace the TODO
comment with your actual user registration code.