Held-Hintergrund ohne Trennlinie
Richtlinien

Mass Affectation

À l'heure actuelle, nous allons passer en revue les vulnérabilités de Mass Assignment et leur apparence, ainsi que quelques moyens de les éviter. Tout d'abord, un petit récapitulatif :

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

Cette vulnérabilité peut survenir lors de l'utilisation d'une bibliothèque/framework qui permet la liaison automatique de paramètres HTTP sur un modèle qui est ensuite utilisé sans aucune validation.

L'utilisation de la liaison automatique d'une requête à un objet peut parfois être 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 utiliser l'exemple d'une page Web où un utilisateur peut modifier des informations, comme son nom, son adresse e-mail et d'autres informations similaires. Nous avons un modèle utilisateur défini comme suit :

classe publique UserModel {

ID long public {get ; set ;}
nom de chaîne publique {get ; set ;}
chaîne publique PasswordHash {get ; set ;}
chaîne publique EmailAddress {get ; set ;}
public bool IsAdmin {get ; set ;}

}

La partie frontale définit un formulaire comme suit. Notez l'absence de la valeur `IsAdmin` :

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

Le contrôleur définit un point de terminaison comme suit. En ayant le `UserModel` comme paramètre, notre framework mappera automatiquement les propriétés respectives sur ce modèle pour nous :

[HTTP Post]
public bool UpdateUser (modèle UserModel)
{
//Assurez-vous que l'utilisateur ne se met à jour que lui-même
Model.ID = Request.User.UserID ;

var success = UserService.UpdateUser (modèle) ;

succès du retour ;
}

À partir de là, nous pouvons supposer que la méthode 'UserService.updateUser' n'effectue aucune validation supplémentaire en termes d'autorisation et enregistre simplement l'objet utilisateur fourni.

(Si aucune valeur n'est fournie pour une propriété, elle conserve simplement la valeur existante)

Cela signifie qu'un utilisateur pourrait soumettre une demande avec le « IsAdmin », ce qui remplacerait la valeur actuelle et ferait 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'affectation massive.

É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 lors de la communication avec un client. La gestion de la soumission d'un formulaire à un contrôleur est une préoccupation très différente de la persistance des données dans une base de données et de la façon dont elles sont représentées dans la base de données. Cela crée un niveau de couplage entre le frontend et la couche de persistance bien plus élevé que ce qui est normal.

Soyez explicite dans vos mappages

Le problème avec la liaison automatique (ou mappage) est que l'absence de mappages explicites permet d'exposer facilement des propriétés qui ne sont pas censées être accessibles sur le modèle. En étant explicite dans les mappages entre les modèles de requêtes et le reste de votre backend, vous pouvez empêcher ces types d'expositions dès le départ.

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

D'autres exemples

Ci-dessous, nous avons quelques exemples supplémentaires dans différentes langues de ce à quoi cela peut ressembler.

C# - non sécurisé

utilisateur de classe publique {
ID long public {get ; set ;}
chaîne publique FirstName {get ; set ;}
chaîne publique LastName {get ; set ;}
chaîne publique PasswordHash {get ; set ;}
chaîne publique Country {get ; set ;}
chaîne publique Role {get ; set ;}
}

[HTTP Post]
public ViewResult Edit (utilisateur utilisateur)
{
//Sauvegarde simplement l'utilisateur tel qu'il est fourni
UserService.UpdateUser (utilisateur) ;
renvoie Ok () ;
}

C# - sécurisé

utilisateur de classe publique {
ID long public {get ; set ;}
chaîne publique FirstName {get ; set ;}
chaîne publique LastName {get ; set ;}
chaîne publique PasswordHash {get ; set ;}
chaîne publique Country {get ; set ;}
chaîne publique Role {get ; set ;}
}
classe publique UpdateUserViewModel {

chaîne publique FirstName {get ; set ;}
chaîne publique LastName {get ; set ;}
chaîne publique Country {get ; set ;}
}

[HTTP Post]
PublicViewResult Edit (UpdateUserViewModel UserModel UserModel)
{
var user = Request.User ;

User.FirstName = UserModel.FirstName ;
User.LastName = UserModel.LastName ;
Utilisateur.country = UserModel.country ;

UserService.UpdateUser (utilisateur) ;

renvoie Ok () ;
}

C# - alternative - exclut les paramètres

utilisateur de classe publique {
ID long public {get ; set ;}
chaîne publique FirstName {get ; set ;}
chaîne publique LastName {get ; set ;}
chaîne publique PasswordHash {get ; set ;}
chaîne publique Country {get ; set ;}
chaîne publique Role {get ; set ;}
}

[HTTP Post]
public ViewResult Edit ([Bind (Include = « FirstName, LastName, Country »)] Utilisateur utilisateur)
{
if (Request.User.Id ! = ID utilisateur) {
return Forbidden (« Demande de changement d'utilisateur ») ;
}
var ExistingUser = Request.User ;
Utilisateur.PasswordHash = Utilisateur.PasswordHash existant ;
Utilisateur.role = Utilisateur.role existant ;

UserService.UpdateUser (utilisateur) ;

renvoie Ok () ;
}

Java - non sécurisé

utilisateur de classe publique {
aide publique à l'information ;
chaîne publique FirstName ;
chaîne publique LastName ;
mot de passe public String PasswordHash ;
pays public sous forme de chaîne ;
rôle de chaîne publique ;
}

@RequestMapping (valeur = « /UpdateUser », méthode = RequestMethod.POST)
public String UpdateUser (utilisateur utilisateur) {
UserService.update (utilisateur) ;
renvoie « UserUpdatedPage » ;
}


Java - sécurisé

classe publique UserViewModel {
chaîne publique FirstName ;
chaîne publique LastName ;
pays public sous forme de chaîne ;
}
utilisateur de classe publique {
aide publique à l'information ;
chaîne publique FirstName ;
chaîne publique LastName ;
mot de passe public String PasswordHash ;
pays public sous forme de chaîne ;
rôle de chaîne publique ;
}
@RequestMapping (valeur = « /UpdateUser », méthode = RequestMethod.POST)
chaîne publique UpdateUser (@AuthenticationPrincipal User CurrentUser, UserViewModel UserViewModel) {
CurrentUser.FirstName = UserViewModel.FirstName ;
CurrentUser.LastName = UserViewModel.LastName ;
CurrentUser.country = UserViewModel.country ;

UserService.update (utilisateur actuel) ;
renvoie « UserUpdatedPage » ;
}

Javascript - non sécurisé

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

Object.assign (utilisateur, req.body) ;

UserService.update (utilisateur) ;

retourner « L'utilisateur a été mis à jour » ;
})

Javascript - sécurisé

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

User.FirstName = req.body.FirstName ;
User.lastName = req.body.LastName ;
user.country = req.body.country ;

UserService.update (utilisateur) ;

retourner « L'utilisateur a été mis à jour » ;
})

Python - Non sécurisé

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

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

pour key, valeur dans form.items () :
setattr (utilisateur, clé, valeur)

UserService.UpdateUser (utilisateur)

redirection de retour (« /user », code=201)

Python - Sécurisé

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

utilisateur = request.user
formulaire = request.form

User.FirstName = Form.FirstName
Utilisateur.LastName = Form.LastName

UserService.UpdateUser (utilisateur)
redirection de retour (« /user », code=201)