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:
public class User {
private String username;
private String password;
private String email;
private boolean isAdmin;
// Getters & Setters
}
The web application implements the following view:
In this case, the browser sends the following request when the form is submitted:
POST /createUser HTTP/1.1
...
username=bob&password=supersecretpassword&email=bob@domain.test
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.
POST /createUser HTTP/1.1
...
username=bob&password=supersecretpassword&email=bob@domain.test&isAdmin=true
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.
public class UserRegistrationFormDTO {
private String userid;
private String password;
private String email;
// NOTE: isAdmin field is not present
// Getters & Setters
}
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.
@Controller
public class UserController
{
@InitBinder
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.setAllowedFields(["userid", "password", "email"]);
}
// ...
}
Auto-binding allow list implementation
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")
}
For example, the UserRegistrationFormDTO below does not contain the isAdmin parameter that might be used to elevate privileges as it is described in the section:
Implement comprehensive input validation for each request parameter, see the page.
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 section: