Lignes directrices

Affectation des masses

Pour l'instant, nous allons examiner les vulnérabilités liées à l'assignation de masse et ce à quoi elles ressemblent, ainsi que quelques moyens de les éviter. Tout d'abord, un bref rappel :

Mass Assignment est une vulnérabilité dans laquelle les points de terminaison de l'API ne limitent pas les propriétés de l'objet associé qui peuvent être modifiées par un utilisateur. 

Cette vulnérabilité peut survenir lors de l'utilisation d'une bibliothèque ou d'un cadre qui permet de lier automatiquement des paramètres HTTP à un modèle qui est ensuite utilisé sans aucune validation. 

L'utilisation de la liaison automatique d'une requête à un objet peut parfois s'avérer extrêmement utile, mais elle peut également entraîner des problèmes de sécurité si le modèle possède des propriétés qui ne sont pas censées être accessibles à l'utilisateur.

Exemple

Nous allons prendre l'exemple d'une page web sur laquelle un utilisateur peut modifier des informations telles que son nom, son adresse électronique et d'autres éléments similaires. Nous avons un modèle User défini comme suit :

public class UserModel {

    public long Id { get; set; }
    public string Name { get; set; }
    public string PasswordHash { get; set; } 
    public string EmailAddress { get; set; } 
    public bool IsAdmin { get; set; }

}

The frontend part defines a form as following. Note the absence of the `IsAdmin` value:

<form method="POST">
     <input name="Id" type="hidden">
     <input name="Name" type="text">
     <input name="EmailAddress" type="text">
     <input type="submit">
</form>  

The controller defines an endpoint as following. By having the `UserModel` as a parameter, our framework will automatically map the respective properties onto this model for us:

[HttpPost]
public bool UpdateUser(UserModel model)
{
    // Ensure the user only updates themselves
    model.Id = Request.User.UserId;

    var success = UserService.UpdateUser(model);

    return success;    
}

À partir de là, nous pouvons supposer que la méthode "UserService.UpdateUser" ne procède à aucune autre validation en termes d'autorisation et se contente d'enregistrer l'objet utilisateur fourni. 

(Si aucune valeur n'est fournie pour une propriété, la valeur existante est conservée.) 

Cela signifie qu'un utilisateur peut soumettre une demande avec la valeur "IsAdmin", qui remplacera la valeur actuelle et fera de l'utilisateur un administrateur comme suit :

<form method="POST">
     <input name="Id" type="hidden" value="666">
     <input name="Name" type="text" value="Bad guy">
     <input name="EmailAddress" type="text" value="hacker@attacker.com">
     <input name="IsAdmin" type="hidden" value="true">
     <input type="submit">
</form>  

Stratégies d'atténuation

Vous trouverez ci-dessous quelques stratégies d'atténuation à prendre en compte pour éviter les vulnérabilités liées à l'attribution de masse.

Évitez de réutiliser les modèles de données pour les modèles de demande

Il est important de séparer vos modèles de données (qui peuvent être conservés dans une base de données) des modèles utilisés pour communiquer avec un client. Le traitement d'un formulaire soumis à un contrôleur est très différent de la persistance des données dans une base de données et de la manière dont elles sont représentées dans la base de données. Cela crée un niveau de couplage beaucoup plus élevé qu'il n'est souhaitable entre le frontend et la couche de persistance. 

Soyez explicite dans vos correspondances

Le problème de la liaison automatique (ou du mappage) est que l'absence de mappage explicite facilite l'exposition de propriétés qui ne sont pas censées être accessibles sur le modèle. En étant explicite dans les mappings entre les modèles de requête et le reste de votre backend, vous pouvez prévenir ces types d'exposition dès le départ.

Pour ce faire, vous pouvez utiliser des modèles différents pour les demandes et les données. Cela ne vous empêche pas d'utiliser un mappeur automatique entre le modèle de demande et le modèle de données, car votre modèle de demande ne doit pas exposer des propriétés qui ne sont pas autorisées pour la demande spécifique.

Autres exemples

Vous trouverez ci-dessous quelques exemples supplémentaires dans différentes langues. 

C# - peu sûr

public class User { 
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PasswordHash { get; set; }
    public string Country { get; set; }
    public string Role { get; set; }
}

[HttpPost]
public ViewResult Edit( User user)
{
    // Just saves the user as provided
    UserService.UpdateUser(user);
    return Ok();
}

C# - sécurisé

public class User { 
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PasswordHash { get; set; }
    public string Country { get; set; }
    public string Role { get; set; }
}
public class UpdateUserViewModel {

    public string FirstName { get; set; }
    public string LastName { get; set; }   
    public string Country { get; set; }
}

[HttpPost]
public ViewResult Edit(UpdateUserViewModel userModel)
{
    var user = Request.User;

    user.FirstName = userModel.FirstName;
    user.LastName = userModel.LastName;
    user.Country = userModel.Country;

    UserService.UpdateUser(user); 

    return Ok();
}

C# - alternative - exclut les paramètres

public class User { 
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string PasswordHash { get; set; }
    public string Country { get; set; }
    public string Role { get; set; }
}

[HttpPost]
public ViewResult Edit([Bind(Include = "FirstName,LastName,Country")] User user)
{
    if(Request.User.Id != user.Id) {
        return Forbidden("Requesting changing of another user");
    }
    var existingUser = Request.User;
    user.PasswordHash = existingUser.PasswordHash;
    user.Role = existingUser.Role;

    UserService.UpdateUser(user);    

    return Ok();
}

Java - peu sûr

public class User {
    public int id;
    public String firstName;
    public String lastName;
    public String passwordHash;
    public String country;
    public String role;
}

@RequestMapping(value = "/updateUser", method = RequestMethod.POST)
public String updateUser(User user) {
        userService.update(user);
        return "userUpdatedPage";
}


Java - sécurisé

public class UserViewModel {
   public String firstName;
   public String lastName;
   public String country;
}
public class User {
   public int id;
   public String firstName;
   public String lastName;
   public String passwordHash;
   public String country;
   public String role;
}
@RequestMapping(value = "/updateUser", method = RequestMethod.POST)
public String updateUser(@AuthenticationPrincipal User currentUser, UserViewModel userViewModel) {
       currentUser.firstName = userViewModel.firstName;
       currentUser.lastName = userViewModel.lastName;
       currentUser.country = userViewModel.country;
       
       userService.update(currentUser);
       return "userUpdatedPage";
}

Javascript - non sécurisé

app.get('/user/update',  (req, res) => {
    var user = req.user;

    Object.assign(user, req.body);

    UserService.Update(user); 

    return "User has been updated";
})

Javascript - sécurisé

app.get('/user/update',  (req, res) => {
    var user = req.user;

    user.firstName = req.body.firstName;
    user.lastName = req.body.lastName;
    user.country = req.body.country;

    UserService.Update(user);

    return "User has been updated";
})

Python - Insécurisé

@app.route("/user/update", methods=['POST'])
def update_user() :

user = request.user
form = request.form.to_dict(flat=True)

for key, value in form.items() :
setattr(user, key, value)

UserService.UpdateUser(user)

return redirect("/user", code=201)

Python - Sécurisé

@app.route("/user/update", methods=['POST'])
def update_user() :

user = request.user
form = request.form

user.firstName = form.firstName
user.lastName = form.lastName

UserService.UpdateUser(user)
return redirect("/user", code=201)