Mass Assignment y GraphQL: cómo se promueve admin con un campo extra
· 10 min de lectura
Mass Assignment permite modificar campos sensibles enviando atributos extra en una petición. GraphQL añade problemas propios: introspection, batching, aliases. Aprende a detectar y mitigar ambos.
La lección anterior del bloque API (BOLA) cubrió el fallo más común en APIs modernas: autorización por objeto rota. Esta cubre dos categorías hermanas que completan el panorama: Mass Assignment / BOPLA (property-level assignment sin whitelist) y los fallos específicos de GraphQL (introspection expuesta, batching sin límites, aliases como amplificador, authorization en resolvers). Ambos siguen produciendo CVEs y bug bounties críticos todos los meses en 2026.
Mass Assignment es un fallo de diseño: el backend acepta cualquier campo que llegue en el body y lo mapea al modelo sin filtrar. Si el modelo tiene campos sensibles (is_admin, role, balance), un atacante los setea con un PATCH extra. GraphQL es un lenguaje de consulta que, mal implementado, permite al cliente pedir campos sensibles que el backend no pensaba exponer, hacer batching de peticiones para bypass de rate limiting, o usar aliases para amplificar enumeración. Las dos técnicas son conocidas desde 2010-2015 pero siguen presentes en APIs modernas por la combinación de frameworks demasiado «mágicos» y revisión de código insuficiente.
Esta lección es la segunda del subcluster APIs de la Tanda 4 (tras BOLA de la 3). Es más corta porque los conceptos clave son pocos pero sus permutaciones son muchas; el valor está en reconocer el patrón en tu propia aplicación.
Prueba solo contra APIs autorizadas. Promover a admin en producción ajena es intrusión y delito.
Mass Assignment#

El patrón es el mismo en todos los frameworks modernos:
# Django
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__' # ← problema: incluye is_admin
def update_user(request, user_id):
user = User.objects.get(id=user_id)
serializer = UserSerializer(user, data=request.data, partial=True)
serializer.is_valid()
serializer.save()
Un atacante envía:
PATCH /api/users/me HTTP/1.1
Content-Type: application/json
Authorization: Bearer <token-del-atacante>
{"email": "[email protected]", "is_admin": true}
Si el serializer acepta is_admin, el usuario queda como admin. El frontend nunca permite editar ese campo, pero la API lo acepta porque no hay whitelist explícita.
Frameworks con el patrón#
- Django REST Framework:
fields = '__all__'o inclusión demasiado amplia. - Rails:
User.new(params[:user])sinstrong_parameters. - Laravel:
$user->fill($request->all())sin$fillableo con$guarded = []. - Express.js:
Object.assign(user, req.body)o uso directo dereq.body. - Spring:
@RequestBodycon bean que incluye todos los campos. - ASP.NET: Model binding a clases que incluyen campos sensibles.
En cada uno hay forma «correcta» (whitelist, strong_parameters, DTOs específicos) y forma «rápida» que suele filtrarse en el primer prototipo y queda para siempre.
Campos sensibles clásicos#
is_admin,role,is_staff,permissions.balance,credits,points,subscription_type.user_id(si lo acepta, suplantas a otro usuario).email_verified,two_factor_enabled(bypass de verificación).created_at,updated_at(forgery de timestamps).password_reset_token(si el modelo lo expone).
Detección#
- Enumeración de campos: enviar POST/PATCH con campos arbitrarios y observar si la respuesta JSON los devuelve.
- Diff entre GET y PATCH: campos que GET devuelve pero PATCH no documenta oficialmente son candidatos.
- Fuzzing con wordlist: diccionarios de nombres típicos (
is_admin,role,superuser). - Revisión de documentación: Swagger/OpenAPI que exponga el modelo entero sin marcar readonly.
- Herramientas:
arjunpara parámetros HTTP, scripts custom con la wordlist.
Mitigación#
- DTOs específicos por acción:
UpdateProfileDTOsolo tiene los campos editables. - Strong parameters (Rails) / allowed fields (DRF) / $fillable (Laravel) siempre.
- Readonly flags para campos sensibles.
- Tests específicos: cada endpoint tiene test que verifica que los campos sensibles no se pueden cambiar.
- Code review: cualquier
fill(),update(),assign()con input de usuario debe revisarse.
GraphQL: la superficie#

GraphQL es un lenguaje de consulta donde el cliente pide exactamente los campos que quiere. Esta flexibilidad abre varios vectores.
Introspection#
GraphQL expone su propio esquema por defecto:
query { __schema { types { name fields { name type { name } } } } }
Herramientas como GraphQL Voyager o InQL muestran el esquema completo gráficamente. Un atacante sabe desde el minuto uno qué types, queries y mutations existen. En producción, introspection debería estar desactivada.
Authorization en resolvers#
GraphQL tiende a mezclar lógica de negocio con resolvers que pueden olvidar verificación de permisos:
// resolvers.js
user: (parent, args, context) => {
return db.user.findById(args.id) // ← sin verificar que es el usuario autenticado
}
Equivalente a BOLA pero en GraphQL. Mismo problema, mismo fix: verificar propietario antes de devolver.
Batching#
GraphQL permite enviar múltiples queries en una sola petición:
query {
user1: user(id: 1) { email }
user2: user(id: 2) { email }
user3: user(id: 3) { email }
...
user1000: user(id: 1000) { email }
}
Un solo request fuerza 1000 operaciones. Si hay rate limiting por petición, se bypasea. Si hay BOLA, se enumera a velocidad enorme.
Aliases como amplificador#
Las aliases user1:, user2: son GraphQL nativo. Combinadas con batching permiten:
- Ejecutar
login1000 veces con passwords distintos en una sola petición (brute force bypass). - Ejecutar mutations idempotentes 1000 veces para race conditions.
- Enumerar IDs masivamente.
Depth limiting#
Sin límite de profundidad, un atacante puede hacer queries recursivas que agotan CPU:
query {
user(id: 1) {
friends { friends { friends { friends { friends { ... } } } } }
}
}
DoS por profundidad. Mitigación: max depth configurado.
Field suggestions#
Algunos servidores devuelven sugerencias cuando un campo no existe:
Did you mean "password"?
Facilita enumerar el esquema incluso con introspection deshabilitada.
Herramientas específicas de GraphQL#
- InQL: Burp extension, introspection + query generator + scanner.
- GraphQL Voyager: visualización interactiva del schema.
- GraphQL Cop: scanner de seguridad.
- Clairvoyance: inferir schema cuando introspection está off.
- graphql-weakness: fingerprint del server.
Flujo típico:
# Detectar GraphQL
curl https://api/graphql -d '{"query":"{__typename}"}'
# Introspection
curl https://api/graphql -d @introspection-query.json
# Si introspection off, inferir con clairvoyance
clairvoyance https://api/graphql -o schema.json
# Scanner
graphql-cop -t https://api/graphql
Walkthrough de ejemplo#
Para fijar el patrón, un escenario típico:
Paso 1 — detectar GraphQL en /graphql.
Paso 2 — introspection: deshabilitada, bien.
Paso 3 — intentar query con campo tentativo:
query { user(id: 1) { email passwordHash } }
Devuelve Cannot query field "passwordHash" on type "User". Did you mean "password"?. Field suggestion activo: se filtra que existe password.
Paso 4 — pedir el campo correcto:
query { user(id: 1) { email password } }
Si no hay authorization y el resolver devuelve el campo, tienes password hash de cualquier usuario.
Paso 5 — amplificar con batching:
query {
u1: user(id: 1) { email password }
u2: user(id: 2) { email password }
...
}
En un solo request, miles de credenciales.
Mitigación GraphQL#
- Introspection OFF en producción.
- Depth limit (p. ej. 5).
- Query cost analysis: cada campo tiene coste, se bloquean queries con coste > N.
- Max aliases por request.
- Rate limiting por operación, no solo por request.
- Authorization en cada resolver, no solo en el edge.
- Allowlist de queries (persisted queries) para APIs internas.
Casos reales famosos#
| Año | Empresa | Vector |
|---|---|---|
| 2012 | GitHub | Mass Assignment en Ruby/Rails: public_key cambiada, acceso a cualquier repo |
| 2017 | Mass Assignment permitía cambiar email asociado a cuenta ajena | |
| 2020 | GitHub | Introspection GraphQL en endpoints privados |
| 2021 | Shopify | GraphQL batching sin límite → brute force de tokens |
| 2022 | Varios SaaS | BOPLA en APIs PATCH con PATCH ignorando permisos por campo |
| 2023 | GitLab | GraphQL field suggestions filtraban schema interno |
El patrón es el mismo: framework «mágico» que mapea body a modelo, o GraphQL «flexible» que entrega todo lo que se pida. La corrección en cada caso es simple y específica, pero la deuda técnica se acumula en empresas que no la vigilan.
Combinación con otros vectores#
Mass assignment combinado con BOLA es devastador:
- Paso 1 — BOLA: lee datos ajenos.
- Paso 2 — Mass Assignment: cambia el propietario (campo
user_id) a ti. - Paso 3 — Control total del recurso ajeno con tu cuenta.
En GraphQL combinado:
- Paso 1 — Introspection: mapea todos los types.
- Paso 2 — Field discovery: encuentra
passwordHashen User. - Paso 3 — Batching: 10 000
user(id: N) { passwordHash }en una petición. - Paso 4 — Crack: los hashes débiles caen con hashcat.
Son cadenas que convierten un fallo «aislado» en brecha total. Por eso la revisión de seguridad de APIs debe ser holística, no por endpoint.
Errores comunes del defensor#
- Introspection «desactivada» pero el schema se deduce de sugerencias (field suggestions on).
- Rate limiting por request y no por operación.
- Authorization en gateway, no en resolvers individuales.
- Mass assignment porque «el frontend no lo pedirá».
- DTOs demasiado amplios porque «qué molesto crear uno por endpoint».
Preguntas frecuentes#
¿GraphQL es más o menos seguro que REST?#
Igual de seguro en teoría. En la práctica, GraphQL falla más por su flexibilidad: cada query es distinta, las defensas basadas en patrones genéricos son insuficientes. REST tiene menos superficie pero cada endpoint tiene sus propios fallos.
¿Un API Gateway (AWS AppSync, Hasura) resuelve esto?#
Ayudan: introspection configurable, rate limits, depth limits, authorization pre-built. Pero no te ahorran revisar los resolvers y la lógica de negocio.
¿Mass Assignment aplica a formularios HTML?#
Sí, era el vector histórico original: formularios con <input type="hidden" name="is_admin" value="false"> que el atacante cambia a true. Hoy es mayormente API, pero el principio es el mismo.
¿GitHub tuvo bugs de GraphQL famosos?#
Sí: en 2020 un bug de introspection en GitHub permitía ver schemas privados. En 2021 un batching sin límite permitía brute force en endpoints de auth. Son el patrón recurrente.
¿Dónde practico?#
El laboratorio profilepeek de whoami-labs tiene una API con GraphQL y REST. Practicas mass assignment PATCH con is_admin:true, introspection via GraphQL, batching con aliases y defensa correcta en los resolvers.
¿Hay herramientas automáticas para detectar mass assignment?#
Sí, varias: arjun para enumeración de parámetros HTTP, ParamSpider para historia pública, extensiones de Burp (Auto Repeater, Autorize) para probar variaciones. Las mejores integran fuzzing de campos sensibles contra responses del GET para identificar atributos que la API acepta pero no documenta. En CI, SAST modernos (Semgrep, SonarQube) tienen reglas para detectar fill(all), __all__, y patrones equivalentes en código.
¿GraphQL federado cambia los riesgos?#
GraphQL federado (varios servicios que componen un supergraph) añade complejidad: cada subgrafo tiene sus propios resolvers con su propia authorization. Un fallo en uno, propagado al supergraph, afecta a todos. Netflix, Shopify y otras empresas con GraphQL federado tienen equipos dedicados a auditar gateways y subgraphs por separado.
¿Cómo convenzo a mi equipo de pasar a DTOs específicos?#
Mostrando CVEs reales de empresas conocidas, no sermoneando. Un ejemplo de GitHub o Instagram con el mismo framework que usáis vale más que cualquier charla teórica. Y un test que demuestra que podéis promover a admin en vuestro propio API con un curl simple suele cerrar la discusión en segundos.
Resumen#
- Mass Assignment (OWASP API BOPLA): backend acepta cualquier campo del body y lo asigna al modelo. Fix: DTOs específicos y whitelist.
- GraphQL: introspection expone schema, batching bypass de rate limits, aliases amplifican enumeración, resolvers sin auth son BOLA en GraphQL.
- Herramientas: InQL, Clairvoyance, GraphQL Cop, scripts custom.
- Mitigación: introspection off, depth limit, query cost, rate limit por operación, authorization en cada resolver.
- GraphQL no es más seguro que REST; es distinto. Requiere revisión específica.