Email uniqueness not properly verified - Go

Email uniqueness not properly verified - Go

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 attacker+1@gmail.com, attacker+2@gmail.com, attacker+3@gmail.com, etc., and all these emails will point to the same attacker@gmail.com 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.

References