Bola Idor in Gin with Bearer Tokens
Bola Idor in Gin with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API exposes an endpoint that accepts user-supplied identifiers and returns or modifies data without verifying that the requesting identity is authorized for that specific resource. In a Gin-based service that uses Bearer Tokens for authentication, this commonly arises when an endpoint like /users/:userID/profile or /accounts/:accountID only checks that a valid Bearer Token exists, but does not confirm that the subject (sub) or other claims within the token refer to the requested :userID or :accountID. Because the authentication check is decoupled from the authorization check, an attacker can copy a valid Bearer Token issued to another user and iterate over predictable resource IDs to access or manipulate data across accounts.
Endpoints that combine Bearer Token validation with numeric or sequential IDs are especially prone to BOLA. For example, a token may contain sub: "usr_1001", but the handler reads param.Arg("userID") directly and uses it in a database query without comparing it to the token’s subject. MiddleBrick’s scan would flag this as a BOLA/IDOR finding because the unauthenticated attack surface allows enumeration of valid IDs and unauthorized data access. The presence of Bearer Tokens does not inherently prevent BOLA; it is the lack of a per-request ownership or scope check that creates the vulnerability.
In Gin, this often maps to the OWASP API Top 10 API1:2023-BROKEN_OBJECT_LEVEL_AUTHORIZATION and can intersect with insecure direct object references (IDOR). If the API also exposes related endpoints such as /accounts/:accountID/transactions or /organizations/:orgID/members, and none of them validate that the token’s claims align with the path parameter, an attacker can traverse relationships and access data outside their scope. Even with Bearer Tokens, without scoping each request to the token’s permissions and resource ownership, the API remains vulnerable to unauthorized read and write operations.
Bearer Tokens-Specific Remediation in Gin — concrete code fixes
To fix BOLA in Gin when using Bearer Tokens, enforce that every request validates not only the token’s authenticity but also that the resource being accessed belongs to the token’s subject or authorized scopes. Below are concrete, working examples that demonstrate a secure pattern.
Example 1: Validate token subject against path parameter
Parse the Bearer Token, extract the subject claim, and compare it to the ID in the route before querying the datastore.
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
Sub string `json:"sub"`
jwt.RegisteredClaims
}
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
auth := c.GetHeader("Authorization")
if auth == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization header required"})
return
}
parts := strings.Split(auth, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid authorization header format"})
return
}
tokenStr := parts[1]
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
// TODO: provide your key function, e.g., RSA public key or HMAC secret
return []byte("your-secret-key"), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Set("claims", claims)
c.Next()
}
}
func GetProfile(c *gin.Context) {
claims, _ := c.Get("claims")
tokenSub := claims.(*Claims).Sub
userID := c.Param("userID")
if tokenSub != userID {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "you can only access your own profile"})
return
}
// Proceed to fetch profile for userID knowing tokenSub == userID
c.JSON(http.StatusOK, gin.H{"profile_for": userID})
}
Example 2: Scope-based authorization with roles and accounts
For endpoints that involve an account or organization ID, compare token claims to ensure the token grants access to that specific account.
func AccountMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
auth := c.GetHeader("Authorization")
if auth == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "authorization header required"})
return
}
parts := strings.Split(auth, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid authorization header format"})
return
}
tokenStr := parts[1]
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("your-secret-key"), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Set("claims", claims)
c.Next()
}
}
type AccountClaims struct {
Sub string `json:"sub"`
Orgs []string `json:"orgs"`
Scopes []string `json:"scopes"`
jwt.RegisteredClaims
}
func AccessAccount(c *gin.Context) {
claims, _ := c.Get("claims")
tokenOrgs := claims.(*AccountClaims).Orgs
accountID := c.Param("accountID")
hasAccess := false
for _, org := range tokenOrgs {
if org == accountID {
hasAccess = true
break
}
}
if !hasAccess {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "token does not grant access to this account"})
return
}
// Proceed to handle accountID safely with token-based scope verification
c.JSON(http.StatusOK, gin.H{"access_to_account": accountID})
}
Additional recommendations include using short-lived tokens with refresh mechanisms, enforcing scopes that map to endpoints, and auditing logs to detect enumeration attempts. MiddleBrick can validate these patterns by scanning your Gin endpoints and confirming that authorization checks are tied to the token context, reducing the likelihood of BOLA/IDOR.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |