Skip to main content

Security controls bypass or absence - Session Invalidation

Need

Implementation of proper session invalidation mechanisms

Context

  • Usage of Go 1.16 for building high-performance and concurrent 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"
)

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

r.GET("/user/main", func(c *gin.Context) {
sessionCode := c.Query("sessionCode")

if sessionCode == "auto" {
c.JSON(200, gin.H{
"message": "Access granted",
})
} else {
c.JSON(401, gin.H{
"message": "Access denied",
})
}
})

r.Run()
}

In the above code, a simple Gin server is created with one route "/user/main". This route is intended to return the user's main page based on their session code.

However, there's a vulnerability in this code. If the provided session code is "auto", the server automatically grants access. This is a security control bypass vulnerability because it allows unauthorized users to gain access by using a specific session code ("auto"). This issue occurred because the code used in the debugging phase, which allowed for easy access, was not removed or properly secured before deployment.

In a real-world scenario, session codes should be unique and tied to individual user sessions. They should also be checked against a database or some form of storage to validate the session and the associated user. This vulnerability allows anyone who knows about the "auto" session code to gain access, bypassing any other security measures in place.

Steps

  • Remove the debugging code that allows access with the session code 'auto'
  • Implement a proper session management mechanism
  • Ensure that session codes are securely generated and validated
  • Use strong and unique session codes for each user
  • Implement session invalidation logic to revoke access when necessary

Compliant code

package main

import (
"github.com/gin-gonic/gin"
"crypto/rand"
"encoding/base64"
"net/http"
)

var sessionStore = make(map[string]string)

func generateSession() string {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(b)
}

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

r.POST("/login", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

// Validate the username and password
if username == "validUser" && password == "validPassword" {
session := generateSession()
sessionStore[session] = username
c.JSON(http.StatusOK, gin.H{
"message": "Logged in",
"session": session,
})
} else {
c.JSON(http.StatusUnauthorized, gin.H{
"message": "Invalid credentials",
})
}
})

r.GET("/user/main", func(c *gin.Context) {
session := c.Query("session")

if user, ok := sessionStore[session]; ok {
c.JSON(http.StatusOK, gin.H{
"message": "Access granted",
"user": user,
})
} else {
c.JSON(http.StatusUnauthorized, gin.H{
"message": "Access denied",
})
}
})

r.Run()
}

The updated code now includes a proper session management mechanism. The debugging code that allowed access with the session code 'auto' has been removed.

A new endpoint /login has been added. This endpoint accepts a POST request with a username and password. If the credentials are valid, a new session is generated using the generateSession function. This function generates a secure, random 32-byte value which is then base64 encoded to create the session code. This session code is stored in the sessionStore map along with the username.

The /user/main endpoint now checks the provided session code against the sessionStore map. If the session code is found in the map, access is granted and the username associated with the session code is returned. If the session code is not found in the map, access is denied.

This code ensures that session codes are securely generated and validated, and that each user has a unique session code. It also implements session invalidation logic by removing session codes from the sessionStore map when necessary.

References