Many modern software development frameworks bind user input directly into internal objects. This is known as mass parameter assignment or auto-binding. While this feature is helpful to assign parameters to objects, an attacker can use it to write fields that should not be set by a user, leading to privilege escalation, data tampering, bypass of security mechanisms, and more.
For example, consider a Java Spring web application with a User object:
The controller that handles the creation request (Spring provides the automatic bind with the User model):
In this case, the browser sends the following request when the form is submitted:
Due to the auto-binding, an attacker can add the isAdmin parameter to the request, which the controller will automatically bind to the model. As a result, the request below will create a user with administrative privileges.
This page contains recommendations for the implementation of protection against mass parameter assignment attacks.
General
Use Data Transfer Objects instead of binding parameters directly to internal objects.
Clarification
A Data Transfer Object (DTO) is an object which only contains attributes that can be modified by a user. It serves as an intermediary between received parameters and a final object. Using Data Transfer Objects makes mass parameter assignment or auto-binding completely safe because they do not contain additional parameters that should not be passed by a user.
For example, the UserRegistrationFormDTO below does not contain the isAdmin parameter that might be used to elevate privileges as it is described in the Overview section:
If there is no possibility to use DTOs, use an allow list and non-sensitive parameters to restrict auto-binding only to those. Avoid using block lists.
Example
In the example below, the binding function uses a set of allowed fields, restricting any other field to be auto-bound and preventing the mass parameter assignment attack.
Implement comprehensive input validation for each request parameter, see the Input Validation page.
Auto-binding allow list implementation
Gin Web Framework allows mass parameter assignment with functions such as Bind(), shouldBind() or mustBind(). These functions rely on the form: tag in a struct and only attributes tagged with form: will be considered safe for auto-binding. Use the form tag to mark parameters that are safe to be auto-bound.
For example, the User struct below does not have the form: tag for the IsAdmin parameter. It means that IsAdmin can not be auto-bound and used to elevate privileges as it is described in the Overview section:
POST /createUser HTTP/1.1
...
username=bob&password=supersecretpassword&email=bob@domain.test
POST /createUser HTTP/1.1
...
username=bob&password=supersecretpassword&email=bob@domain.test&isAdmin=true
public class UserRegistrationFormDTO {
private String userid;
private String password;
private String email;
// NOTE: isAdmin field is not present
// Getters & Setters
}
@Controller
public class UserController
{
@InitBinder
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.setAllowedFields(["userid", "password", "email"]);
}
// ...
}
package main
import (
"log"
"github.com/gin-gonic/gin"
)
type User struct {
Username string `form:"username"`
Password string `form:"password"`
Email string `form:"email"`
IsAdmin bool `default:false`
}
func main() {
route := gin.Default()
route.POST("/createUser", createUser)
route.Run()
}
func createUser(c *gin.Context) {
var user User
if c.ShouldBind(&user) == nil {
log.Println(user.Username)
log.Println(user.Password)
log.Println(user.Email)
log.Println(user.IsAdmin)
}
c.String(200, "Success")
}