Mass Assignment in Gin
How Mass Assignment Manifests in Gin
Mass assignment vulnerabilities in Gin applications occur when user-supplied data is automatically mapped to struct fields without proper validation of which fields should be writable. This is particularly dangerous in Gin because of its tight integration with JSON binding and struct tag processing.
The most common attack pattern involves sending JSON payloads that include fields the attacker shouldn't control. For example:
{
"username": "attacker",
"email": "attacker@example.com",
"is_admin": true,
"account_balance": 9999.99
}
In a typical Gin handler, this payload might be bound to a struct like:
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
PasswordHash string `json:"password_hash"`
IsAdmin bool `json:"is_admin"`
AccountBalance float64 `json:"account_balance"`
}
func createUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Save to database...
}
The vulnerability here is that IsAdmin and AccountBalance fields are exposed to user input. An attacker can escalate privileges or manipulate financial data simply by including these fields in their request.
Gin's binding mechanism uses reflection to map JSON keys to struct fields, which means it will populate all fields that have matching JSON tags, regardless of whether those fields should be user-modifiable. This is especially problematic when dealing with database models that include sensitive fields.
Another Gin-specific manifestation occurs with nested structs and embedded types. Consider:
type Credentials struct {
Password string `json:"password"`
}
type User struct {
Username string `json:"username"`
Credentials
}
func login(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Authentication logic...
}
Here, the password field from the embedded Credentials struct is automatically exposed to mass assignment, potentially allowing attackers to manipulate authentication data.
Gin-Specific Detection
Detecting mass assignment vulnerabilities in Gin applications requires examining both the code structure and the runtime behavior. Here are Gin-specific detection techniques:
Code Analysis Patterns
Look for these patterns in your Gin handlers:
// Vulnerable pattern - no field filtering
var user User
c.ShouldBindJSON(&user)
// Better pattern - explicit field filtering
var input struct {
Username string `json:"username"`
Email string `json:"email"`
}
c.ShouldBindJSON(&input)
// Even better - use dedicated DTOs
type CreateUserRequest struct {
Username string `json:"username" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
func createUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Only use validated fields
user := User{
Username: req.Username,
Email: req.Email,
}
}
Runtime Detection with middleBrick
middleBrick's API security scanner includes specific checks for mass assignment vulnerabilities in Gin applications. It can detect:
- Unrestricted JSON binding in handler functions
- Sensitive fields exposed through struct tags
- Embedded structs that expose additional fields
- Missing validation on critical fields like
is_admin,role,balance
The scanner tests your API endpoints by sending crafted payloads with common sensitive field names and analyzing the responses. For example, it might send:
{
"is_admin": true,
"account_balance": 1000000,
"role": "administrator",
"user_id": 1
}
If your API accepts and processes these fields without validation, middleBrick will flag this as a mass assignment vulnerability with a detailed report showing exactly which fields were vulnerable and how they could be exploited.
Database Model Analysis
middleBrick also analyzes your OpenAPI/Swagger specifications if provided, cross-referencing the declared models with the actual runtime behavior. This helps identify discrepancies between what your API documentation claims and what it actually accepts.
Gin-Specific Remediation
Remediating mass assignment vulnerabilities in Gin requires a combination of architectural patterns and proper use of Gin's features. Here are Gin-specific solutions:
1. Use Data Transfer Objects (DTOs)
Create dedicated request structs that only include fields users should be able to modify:
// Request DTO - only contains user-modifiable fields
type UpdateUserRequest struct {
Username string `json:"username" binding:"required,alpha,min=3,max=50"`
Email string `json:"email" binding:"required,email"`
Bio string `json:"bio" binding:"max=500"`
}
// Domain model - contains all fields including sensitive ones
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"-"`
IsAdmin bool `json:"-"`
AccountRole string `json:"-"`
CreatedAt time.Time `json:"-"`
}
func updateUser(c *gin.Context) {
var req UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Find user from database
user, err := findUserByID(c.Param("id"))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
// Update only allowed fields
user.Username = req.Username
user.Email = req.Email
user.Bio = req.Bio
// Save without risk of overwriting sensitive fields
if err := saveUser(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "User updated successfully"})
}
2. Use Gin's Binding Tags Effectively
Gin supports validation tags that help prevent mass assignment:
type CreateAccountRequest struct {
Username string `json:"username" binding:"required,alpha,min=3,max=50"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
// No IsAdmin, AccountBalance, or other sensitive fields!
}
func createAccount(c *gin.Context) {
var req CreateAccountRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Hash password before storing
hashedPassword, err := bcrypt.GenerateFromPassword(
[]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Password processing failed"})
return
}
// Create user with default values for sensitive fields
user := User{
Username: req.Username,
Email: req.Email,
PasswordHash: string(hashedPassword),
IsAdmin: false,
AccountRole: "user",
AccountBalance: 0.0,
}
if err := saveUser(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create account"})
return
}
c.JSON(http.StatusCreated, gin.H{"message": "Account created"})
}
3. Implement Field-Level Authorization
For update operations, implement fine-grained control over which fields can be modified:
type FieldPermissions struct {
AllowUsernameChange bool
AllowEmailChange bool
AllowBioChange bool
// No permission for IsAdmin, AccountBalance, etc.
}
func updateUser(c *gin.Context) {
userID := c.Param("id")
var req map[string]interface{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Check permissions based on user role
currentUser, _ := getCurrentUser(c)
perms := getUserFieldPermissions(currentUser.Role)
// Find existing user
user, err := findUserByID(userID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
// Update only allowed fields
if perms.AllowUsernameChange {
if val, ok := req["username"].(string); ok {
user.Username = val
}
}
if perms.AllowEmailChange {
if val, ok := req["email"].(string); ok {
user.Email = val
}
}
if perms.AllowBioChange {
if val, ok := req["bio"].(string); ok {
user.Bio = val
}
}
if err := saveUser(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "User updated successfully"})
}
4. Use Database-Level Protection
Combine application-level fixes with database constraints:
// When saving, explicitly set non-user-modifiable fields
func saveUser(user User) error {
// Ensure sensitive fields have correct defaults
user.IsAdmin = false
user.CreatedAt = time.Now()
// Use database transactions to prevent partial updates
db := getDatabaseConnection()
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
// Use parameterized queries or ORM that prevents injection
_, err = tx.Exec(`
INSERT INTO users (username, email, password_hash, is_admin, account_role, account_balance, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (id) DO UPDATE SET
username = EXCLUDED.username,
email = EXCLUDED.email
WHERE users.id = $8
`, user.Username, user.Email, user.PasswordHash, user.IsAdmin, user.AccountRole, user.AccountBalance, user.CreatedAt, user.ID)
if err != nil {
return err
}
return tx.Commit()
}Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |
Frequently Asked Questions
How does middleBrick detect mass assignment vulnerabilities in Gin applications?
middleBrick scans your API endpoints by sending crafted JSON payloads containing common sensitive field names like is_admin, account_balance, role, and user_id. It analyzes the responses to determine if these fields are accepted and processed without proper validation. The scanner also examines your OpenAPI/Swagger specifications to identify discrepancies between declared models and actual runtime behavior. For Gin applications specifically, middleBrick looks for patterns like unrestricted ShouldBindJSON calls and embedded structs that might expose sensitive fields.
Can I integrate middleBrick into my Gin CI/CD pipeline?
Yes, middleBrick offers a GitHub Action that integrates directly into your CI/CD pipeline. You can add it to your workflow to automatically scan your Gin API endpoints before deployment. The action can be configured to fail the build if the security score drops below a threshold you specify. This ensures that mass assignment vulnerabilities and other security issues are caught early in the development process. The GitHub Action works with any API endpoint, including those built with Gin, and provides detailed reports that are accessible from your GitHub repository.