Skip to main content

Privilege escalation

Need

Enhancement of role-based access control to prevent unauthorized privilege escalation

Context

  • Usage of Go 1.16 for building high-performance and scalable applications
  • Usage of gin-gonic/gin for building web applications in Go

Description

Non compliant code

package main

import (
"github.com/gin-gonic/gin"
)

type User struct {
Username string
Password string
Role string
}

var users = []User{
{"admin", "adminpass", "admin"},
{"user", "userpass", "user"},
}

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

r.POST("/login", func(c *gin.Context) {
var u User
if err := c.ShouldBindJSON(&u); err == nil {
for _, user := range users {
if user.Username == u.Username && user.Password == u.Password {
c.JSON(200, gin.H{
"role": user.Role,
})
return
}
}
c.JSON(401, gin.H{"status": "unauthorized"})
} else {
c.JSON(400, gin.H{"status": "bad request"})
}
})

r.Run()
}

In the above code, we have a basic login system for a Go backend application using the Gin framework. The application has two types of users: admin and standard user. Each user has a username, password, and role. The role of the user is returned in the response when a successful login attempt is made.

The vulnerability lies in the fact that the role of the user is not properly checked or enforced in the application. This means that if a standard user can somehow figure out the username and password of an admin user, they can log in as an admin user and gain administrative access to the application.

Moreover, the passwords are stored in plain text within the application code, which is a major security risk. If an attacker gains access to the source code, they can easily see the passwords of all users, including the admin user.

To exploit this vulnerability, an attacker would simply need to send a POST request to the /login endpoint with the username and password of an admin user. The application would then return the role of the user, allowing the attacker to escalate their privileges from a standard user to an admin user.

Steps

  • Implement proper role-based access control (RBAC) mechanism.
  • Ensure that user roles are securely stored and validated.
  • Use a secure authentication mechanism, such as JWT, to prevent unauthorized access.
  • Implement strong password policies and enforce password complexity requirements.
  • Regularly review and update user roles and permissions to minimize the risk of privilege escalation.
  • Implement logging and monitoring to detect and respond to any suspicious activities.
  • Conduct regular security assessments and penetration testing to identify and address any vulnerabilities.

Compliant code

package main

import (
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"time"
)

type User struct {
Username string
Password string
Role string
}

type Claims struct {
Username string `json:"username"`
Role string `json:"role"`
jwt.StandardClaims
}

var users = []User{
{"admin", "adminpass", "admin"},
{"user", "userpass", "user"},
}

var jwtKey = []byte("my_secret_key")

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

r.POST("/login", func(c *gin.Context) {
var u User
if err := c.ShouldBindJSON(&u); err == nil {
for _, user := range users {
if user.Username == u.Username && user.Password == u.Password {
expirationTime := time.Now().Add(5 * time.Minute)
claims := &Claims{
Username: u.Username,
Role: user.Role,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)

if err != nil {
c.JSON(500, gin.H{"status": "internal server error"})
return
}

c.JSON(200, gin.H{
"token": tokenString,
})
return
}
}
c.JSON(401, gin.H{"status": "unauthorized"})
} else {
c.JSON(400, gin.H{"status": "bad request"})
}
})

r.Run()
}

The updated code now implements a secure authentication mechanism using JWT (JSON Web Tokens). When a user logs in, a JWT is created that includes the user's role as a claim. This token is then signed with a secret key and returned to the user. The user must include this token in the header of their requests to access protected routes.

The JWT expires after a certain amount of time (5 minutes in this example), after which the user must log in again to obtain a new token. This helps to prevent unauthorized access if the token is somehow compromised.

The secret key used to sign the JWT should be kept secure and not exposed in the code or any version control systems. In a real-world application, this key would typically be stored in an environment variable or a secure vault.

This code does not yet include the implementation of role-based access control (RBAC) for different routes, strong password policies, logging and monitoring, or regular security assessments and penetration testing. These would need to be implemented as part of a comprehensive security strategy.

References