Análisis del examen de Especialista en Seguridad de la Información Registrado, primavera de Reiwa 6, tarde, pregunta 1 — el alg=none de JWT, la autorización de API y las medidas provisionales de WAF
· Actualizado el: · Go Komura · Especialista en Seguridad de la Información Registrado, Examen SC, API, Seguridad de API, JWT, Autenticación, Autorización, WAF, Log4Shell, Seguridad de la información, Vulnerabilidad, IPA
«Verifico la firma del JWT, así que puedo confiar en el ID de usuario.»
Esta afirmación solo es cierta a medias.
La pregunta 1 de la tarde del examen de Especialista en Seguridad de la Información Registrado, primavera de Reiwa 6 (2024), toma como tema una API invocada desde un smartphone1. Al autenticarse con éxito se emite un JWT, y ese JWT se adjunta para invocar las API de obtención y actualización de la información del usuario. A primera vista, es una configuración habitual.
Sin embargo, el diagnóstico descubre los cuatro problemas siguientes.
- Si se cambia el
algde la cabecera del JWT anone, un JWT sin firma pasa la verificación - Usando un JWT correcto pero cambiando el
mida otro ID de usuario, se puede obtener o actualizar la información de otra persona - Añadiendo un
status=paidque no está en la especificación, un usuario gratuito se convierte en usuario de pago - El código de autenticación de 4 dígitos que llega por correo se puede probar por fuerza bruta sin límite alguno
Los cuatro parecen «vulnerabilidades relacionadas con la autenticación», pero la causa no es la misma. Se rompen límites distintos: la integridad del token, la autorización a nivel de objeto, la autorización a nivel de propiedad y la limitación de intentos.
En la segunda mitad se añade otro punto más. Se hace pública una vulnerabilidad grave en una biblioteca de código abierto ampliamente utilizada, que permite ejecutar código desde el exterior aprovechando un JNDI Lookup. Todavía no existe ni una versión corregida ni una regla de WAF completa. Mientras tanto, se pregunta cómo confirmar el impacto, qué debe observar el WAF y por qué al principio se elige «detección» en lugar de «bloqueo».
En este artículo, tomando como base el ejemplo de respuesta oficial2 y el comentario de calificación3, se ordena no solo la respuesta de cada pregunta, sino por qué es esa la respuesta y hasta qué punto conviene reforzar el diseño en la práctica profesional.
flowchart TB
accTitle: Panorama general del problema
accDescr: Muestra los límites de confianza que se rompen en cada etapa entre el código de autenticación, el JWT, la autorización de la API y la vulnerabilidad de la biblioteca
user[Aplicación del usuario]
auth[Código de autenticación de 4 dígitos]
jwt[Emisión del JWT]
api[API de usuario]
log[Salida de registros]
vuln[Biblioteca vulnerable]
ext[Ejecución remota de código]
user --> auth
auth -->|"Sin límite de intentos"| jwt
jwt -->|"Permite alg=none"| api
api -->|"Confía en mid"| db1[Obtención/modificación de datos de otro usuario]
api -->|"status=paid"| db2[Cambio del estado de facturación]
api --> log
log --> vuln
vuln -->|"JNDI/LDAP/HTTP"| ext
Figura 1: Panorama general del problema. En cada etapa se rompe un límite de confianza distinto.
1. Primero, la conclusión
- Que una API RESTful no tenga sesión es su propiedad stateless. Sin embargo, esto no significa que el servidor no mantenga ningún estado de la base de datos ni del usuario
- El código de 4 dígitos tiene 10.000 combinaciones. A 10 intentos por segundo, el promedio son 5.000 intentos, 500 segundos. Como es menos que el periodo de validez de 10 minutos, solo el tiempo de caducidad no lo evita
- La medida mínima frente a
alg=nonees comprobar que elalgde la cabecera del JWT no seaNONE. Sin embargo, en la práctica conviene fijar en el servidor los algoritmos permitidos - Aunque el JWT sea correcto, no se debe confiar en el
midde la solicitud. Hay que cotejar el ID de usuario contenido en el JWT con elmid, o, de forma más segura, no recibirmidy determinar el objetivo a partir del JWT - Añadir
status=paides un problema de Mass Assignment, que vincula al objeto interno incluso propiedades ajenas a la especificación. Hay que usar el DTO de actualización como una lista de permitidos y no dejar que el usuario cambie el estado de facturación - La respuesta a la contramedida contra la fuerza bruta es el proceso que bloquea la cuenta cuando el número de fallos consecutivos supera un umbral. En la práctica también se añaden el retraso escalonado y el control por origen de la solicitud
- Para confirmar el impacto de una nueva vulnerabilidad grave, en lugar de instrucciones destructivas, se registra el acceso al
index.htmlde un servidor de pruebas para confirmar que se llega a la ejecución remota de código - Como la cadena de ataque va en una cabecera HTTP, el objeto de inspección del WAF es
Header. Como expresión regular que soporte el intercambio de mayúsculas y minúsculas se usa, por ejemplo,\W[jJ][nN][dD][iI]\W - La ventaja de poner primero el WAF en «detección» es que se evita el bloqueo de comunicaciones de negocio por falsos positivos. Al recibir una alerta se examina si es un ataque, y después de ajustar se pasa a bloqueo
- El WAF es una medida provisional; la solución definitiva es actualizar la biblioteca afectada a la versión corregida
2. El escenario y la correspondencia con las preguntas
El escenario del problema es la empresa G, que se dispone a ofrecer un nuevo servicio de salud. Los usuarios introducen desde la aplicación de smartphone datos como la comida y el peso, y reciben una evaluación de riesgo para la salud y recomendaciones de menús. El sistema está construido en la nube y combina una puerta de enlace de API, procesamiento dirigido por eventos y una base de datos gestionada.
Los nombres de productos y servicios del enunciado están abstraídos. Este artículo tampoco reproduce las figuras ni el texto de IPA, sino que parafrasea únicamente la estructura necesaria para entender las preguntas.
| Pregunta | Tema | Capítulo de este artículo |
|---|---|---|
| Pregunta 1 | Propiedades de una API RESTful | Capítulo 4 |
| Pregunta 2(1) | Tiempo de fuerza bruta sobre el código de 4 dígitos | Capítulo 5 |
| Pregunta 2(2) | alg=none del JWT |
Capítulo 6 |
| Pregunta 2(3) | Acceso a otra persona usando mid |
Capítulo 7 |
| Pregunta 2(4) | Falla al aceptar un status fuera de especificación |
Capítulo 8 |
| Pregunta 2(5) | Contramedida contra la fuerza bruta | Capítulo 9 |
| Pregunta 3(1) | Confirmar de forma segura la existencia de la vulnerabilidad | Capítulo 11 |
| Pregunta 3(2)(3) | Dónde observa el WAF y expresión regular | Capítulo 12 |
| Pregunta 3(4) | Ventaja del modo de detección y su operación | Capítulo 13 |
Según el comentario de calificación, la tasa de aciertos global fue promedio. Por otro lado, se señala que la tasa de aciertos fue algo baja en la contramedida contra la alteración del JWT de la pregunta 2(2) y en el mecanismo necesario para el servidor de verificación de la pregunta 3(1). En ambos casos, no basta con conocer las palabras: hace falta seguir qué valor cambia el atacante, hacia qué proceso fluye y en qué punto se confió indebidamente en él.
3. Este problema no es solo un problema de “autenticación”
Si se ordena todo el problema por límites de confianza, queda así.
[ID de usuario y contraseña]
|
v
[Verificación del código de 4 dígitos] ---- sin límite de intentos ----> fuerza bruta
|
v
[Se emite el JWT]
|
v
[Biblioteca JWT] ------- permite alg=none ------> alteración del ID de usuario
|
v
[API de usuario]
| |
| +-- pasa status entero ----> falla de autorización a nivel de propiedad
|
+-- confía en mid -----------------------> falla de autorización a nivel de objeto
[Registra la entrada externa en el log]
|
v
[Biblioteca vulnerable] ---- JNDI/LDAP/HTTP ------> ejecución remota de código
Aquí lo más importante es la siguiente distinción.
| Comprobación | Qué pregunta | Ejemplo roto en este problema |
|---|---|---|
| Autenticación | Quién es usted | Fuerza bruta sobre el código de 4 dígitos |
| Verificación del token | Esa identidad no ha sido alterada | alg=none |
| Autorización a nivel de objeto | Si se puede acceder a los datos de ese usuario | Sustitución de mid |
| Autorización a nivel de propiedad | Si se puede modificar ese elemento | status=paid |
| Límite entre la entrada y la ejecución | Si la entrada externa no se interpreta como una instrucción | JNDI Lookup |
Haber superado la comprobación anterior no es motivo para omitir la siguiente. Un usuario con un JWT correcto no necesariamente puede leer los datos de otra persona. Un usuario que puede actualizar sus propios datos no necesariamente puede cambiar también su estado de facturación.
Con esta separación en etapas, la respuesta de cada pregunta deja de ser memorización.
flowchart LR
accTitle: Diferencia entre autenticación y autorización
accDescr: La autenticación confirma el sujeto y la autorización confirma qué operaciones tiene permitidas ese sujeto
auth[Autenticación<br/>quién es]
authz[Autorización<br/>qué puede hacer]
auth --> authz
Figura 2: Diferencia entre autenticación y autorización. Primero va la autenticación, y la autorización es una comprobación distinta.
4. Pregunta 1 — ¿Qué es “stateless”?
La pregunta 1 plantea uno de los principios de diseño de las API RESTful: la propiedad de no realizar gestión de sesiones.
La respuesta es stateless.
Stateless significa que el servidor no necesita recordar el estado de la conversación de la solicitud anterior, y que cada solicitud, por sí sola, reúne toda la información necesaria para el procesamiento. En este problema, la aplicación de smartphone adjunta el JWT en la cabecera Authorization en cada solicitud. El servidor verifica ese JWT e identifica al usuario de esa solicitud.
Es fácil malinterpretar stateless como «el servidor no guarda ningún estado». En realidad, sí mantiene normalmente los siguientes estados.
- La base de datos que almacena la información del usuario y los datos de salud
- El estado de facturación
- El valor del código de autenticación, su fecha de caducidad y el número de fallos
- La clave de firma del JWT
- Si el diseño usa una lista de revocación, esa información de revocación
- Los registros y las auditorías
Lo que no mantiene es hacer que un estado de sesión del lado del servidor, cuyo único fin sea continuar la conversación, sea un requisito previo de cada llamada a la API.
Además, ser stateless no aumenta automáticamente la seguridad. Enviar el JWT cada vez facilita el escalado horizontal, pero si la verificación del JWT es incorrecta, ese error también se propaga de manera uniforme a todos los nodos. La propiedad arquitectónica y la corrección de seguridad son cosas distintas.
5. Pregunta 2(1) — El código de 4 dígitos se acierta en 500 segundos de media
Si el ID de usuario y la contraseña son correctos, la API de autenticación envía por correo un número de 4 dígitos. Después, si el ID de usuario y el código de 4 dígitos coinciden, se emite el JWT. El código es válido durante 10 minutos desde su generación.
En el diagnóstico fue posible realizar 10 intentos por segundo. Se pide calcular cuántos segundos hacen falta en promedio para vulnerarlo.
El cálculo es “la mitad del número de candidatos”
Un número de 4 dígitos, incluyendo el cero inicial, suma las siguientes 10.000 combinaciones.
0000, 0001, 0002, ... , 9999
Si la respuesta correcta se elige de forma uniforme, el número medio de intentos que necesita un atacante que prueba en orden y sin repetir hasta llegar a la respuesta correcta es la mitad del número de candidatos.
Número medio de intentos = 10.000 ÷ 2 = 5.000 intentos
Tiempo medio = 5.000 ÷ 10 intentos/s = 500 segundos
Por lo tanto, el espacio en blanco b es 500.
En el peor caso llevaría 1.000 segundos, pero lo que pregunta el problema es el promedio. Y el periodo de validez del código es de 600 segundos, más largo que los 500 segundos del tiempo medio de vulneración. Esta es la razón por la que se considera “alta la probabilidad de ser vulnerado”.
flowchart LR
accTitle: Sensación de tiempo del código de autenticación de 4 dígitos
accDescr: Probando 10.000 combinaciones a 10 intentos por segundo, el promedio es 5.000 intentos, 500 segundos, un tiempo menor que el periodo de validez de 600 segundos
A["Número de candidatos: 10.000"] -->|"Promedio 10.000 / 2 = 5.000 intentos"| B["Tiempo medio de vulneración: 500 s"]
C["Periodo de validez: 600 s"] -->|"500 s < 600 s"| D["Se puede vulnerar dentro del periodo de validez"]
Figura 9: Sensación de tiempo del código de autenticación de 4 dígitos. Probando en promedio la mitad de los candidatos, se acierta dentro del periodo de validez.
Aunque se acorte solo el tiempo de caducidad, si hay pocos candidatos se pierde
La fortaleza de un código de autenticación no la determinan solo el número de dígitos ni solo el periodo de validez.
Número de intentos posibles dentro del periodo de validez
= Intentos por segundo × periodo de validez
= 10 × 600
= 6.000 intentos
Si se prueban valores distintos en orden, se puede verificar el 60% de las 10.000 combinaciones dentro del periodo de validez. Aunque exista un tiempo de caducidad, si no se limita el número de intentos, no es suficiente.
La versión vigente de NIST SP 800-63B exige al menos 6 dígitos para los secretos de corta duración usados en autenticación fuera de banda, y hace obligatorio limitar el número de intentos si tienen menos de 64 bits. Además, pide no usar el correo electrónico para autenticación fuera de banda4. En el examen se responde dentro del marco dado de 4 dígitos y envío por correo, pero en un diseño nuevo en la práctica conviene revisar también esa misma premisa.
6. Pregunta 2(2) — alg=none: el problema de “dejar que el atacante elija el método de verificación”
El JWT del problema consta de tres partes: cabecera, payload y firma.
base64url(header).base64url(payload).base64url(signature)
En la cabecera se registraba RS256 como algoritmo usado para la firma. El payload contiene el ID de usuario, la hora de emisión y la fecha de expiración.
El diagnosticador cambió las dos cosas siguientes.
- Cambiar el
algde la cabecera deRS256aNONE - Cambiar el ID de usuario del payload por otro usuario
Al enviar ese JWT, la verificación se completaba con éxito y se lograba suplantar a otra persona.
flowchart LR
accTitle: Flujo del ataque JWT alg=none
accDescr: Cambia el alg de un JWT correcto a none y reescribe el ID de usuario para que pase la verificación
A["JWT correcto<br/>alg=RS256<br/>user=user01"] -->|"Cambia el alg de la cabecera a none"| B["JWT alterado<br/>alg=none<br/>user=user02"]
B -->|"Omite la verificación de firma"| C["El servidor lo acepta<br/>como user02"]
Figura 3: Flujo del ataque alg=none en JWT. El atacante elige el algoritmo de verificación.
none no es un error de escritura
En el RFC 7519 se define el “Unsecured JWT”, un JWT sin firma ni cifrado cuyo alg es none5. Por lo tanto, el valor none no es algo que no exista en absoluto en la especificación.
El problema es que una API que debería aceptar únicamente JWT firmados aceptó el none indicado por el atacante.
El procesamiento vulnerable, escrito de forma conceptual, es el siguiente.
1. Lee la cabecera del JWT
2. Mira el alg escrito en la cabecera y elige el método de verificación
3. Si alg es none, no realiza la verificación de la firma
4. Confía en el ID de usuario del payload
Se deja que la propia fortaleza de la seguridad la elija una entrada controlada por el atacante.
La respuesta del examen
La pregunta plantea, en no más de 20 caracteres cada una, “sobre qué dato” y “qué tipo de verificación” debe realizar la biblioteca Q corregida.
El ejemplo de respuesta es el siguiente.
| Elemento | Punto clave de la respuesta |
|---|---|
| Dato a verificar | El valor indicado en el alg de la cabecera del JWT |
| Contenido de la verificación | Verificar que no sea NONE |
Como corrección directa de la vulnerabilidad del enunciado, esto es correcto.
En la práctica, no basta con “cualquier cosa que no sea NONE”
Aquí conviene separar la respuesta del examen de la recomendación en la práctica profesional.
El RFC 8725 establece que la biblioteca de JWT debe permitir que quien la invoca especifique un conjunto de algoritmos permitidos, y que no debe usarse ningún algoritmo fuera de ese conjunto6. Es decir, la idea es la siguiente.
Enfoque incorrecto:
Aceptar si token.header.alg != "none"
Enfoque correcto:
Aceptar solo si está incluido en serverConfig.allowedAlgorithms
Ejemplo: allowedAlgorithms = ["RS256"]
Rechazar únicamente none puede dejar sin resolver otros algoritmos débiles, o una confusión de algoritmos que mezcla el esquema de clave pública con el de clave simétrica. El principio es no ir sumando condiciones negativas a lo que se acepta, sino fijar de forma estrecha lo que se permite.
En la verificación del JWT, además del algoritmo, según el uso conviene confirmar al menos lo siguiente.
| Elemento | Qué confirmar |
|---|---|
| Firma | Si se puede verificar con la clave y el algoritmo previstos |
iss |
Si es un emisor de confianza |
aud |
Si el token fue emitido para esta API |
exp |
Si está dentro del periodo de validez |
nbf |
Si no es anterior al momento en que empieza a poder usarse |
sub o ID de usuario |
Si es un sujeto válido dentro de la aplicación |
| Tipo de token | Si no se confunde, por ejemplo, un token de identidad con un token de acceso |
En este problema, la clave del payload es user, pero en la práctica conviene usar el estándar sub o definir con claridad el significado de una claim propia.
flowchart TB
accTitle: Verificación de JWT segura frente a insegura
accDescr: La verificación insegura depende del alg, la verificación segura usa una lista de algoritmos permitidos en el servidor
subgraph "Verificación insegura"
D1[Lee el alg de la cabecera del JWT]
D2[Si alg es none, lo acepta]
D1 --> D2
end
subgraph "Verificación segura"
S1["Algoritmo permitido en la configuración del servidor<br/>ej.: RS256"]
S2["¿El alg de la cabecera del JWT<br/>está en la lista de permitidos?"]
S3[Verifica firma, iss, aud, exp]
S1 --> S2 --> S3
end
Figura 4: Verificación segura frente a verificación insegura. En la práctica conviene fijar de forma estrecha los algoritmos permitidos.
Base64url no es cifrado
Hay otro malentendido frecuente sobre el JWT. La cabecera y el payload se representan en base64url, pero esto no es cifrado. Cualquiera puede decodificarlos y leerlos.
Lo que garantiza la firma es que, únicamente cuando se puede verificar correctamente, el contenido no ha sido alterado desde su emisión. Esto no significa que se puedan colocar datos personales que deban mantenerse en secreto dentro del payload de un JWT firmado.
7. Pregunta 2(3) — Incluso con un JWT correcto, cambiar mid permitía leer datos de otra persona
El siguiente es un ataque que no altera el propio JWT.
La API de usuario recibe, mediante GET o PUT, un ID de usuario llamado mid. El módulo común P obtiene o actualiza en la base de datos la información del usuario vinculada a ese mid.
La estructura del ataque es simple.
ID de usuario en el JWT: user01 ← JWT firmado correctamente
mid de la solicitud: user02 ← modificado por el atacante
Como la firma del JWT es correcta, la autenticación tiene éxito. Pero la API confía sin más en mid=user02 y devuelve la información de user02.
Este es un caso típico de lo que en el OWASP API Security Top 10 2023 se denomina Broken Object Level Authorization (BOLA). Cuando se accede a datos usando un ID de objeto que indica el usuario, es necesario confirmar la autorización sobre ese objeto en cada ocasión7.
flowchart LR
accTitle: Ataque BOLA
accDescr: Usa un JWT correcto pero cambia el mid de la solicitud por el ID de otro usuario
A[Atacante] -->|"JWT: user01<br/>mid: user02"| B[API de usuario]
B -->|"Confía en mid"| C[Devuelve desde la BD la información de user02]
Figura 5: Ataque BOLA. La autenticación se supera, pero no se comprueba la autorización.
La respuesta de la pregunta
El subrayado ② de la tabla 5 pregunta, en no más de 40 caracteres, qué proceso añadir a la invocación del módulo común P.
El ejemplo de respuesta es el siguiente.
Proceso que verifica si el ID de usuario contenido en el JWT coincide con el valor de mid
La ventaja de verificarlo en el módulo común P es que se puede aplicar la misma autorización tanto a GET como a PUT, y también a cualquier otra API que use P en el futuro. Si se copia la misma comparación en cada pantalla o cada endpoint, en algún punto se acaba omitiendo.
Un diseño más seguro: no recibir mid
Si la API solo obtiene o actualiza la información del propio usuario, no hace falta recibir el ID de usuario desde el cliente.
GET /users/me
Authorization: Bearer <JWT>
En el lado del servidor, el sujeto se extrae del JWT ya verificado.
principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)
Lo mismo para la actualización.
principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
userId = principal.subject,
name = input.name,
age = input.age
)
La comparación se puede proteger si se escribe. Pero si el diseño no recibe el ID de destino desde el exterior, se reduce el propio tipo de fallo de olvidar escribir esa comparación.
Si un administrador necesita operar sobre la información de otro usuario, conviene separar así.
PUT /users/me para usuarios generales
PUT /admin/users/{userId} para administradores
Para el uso de administrador se exige un permiso distinto, registro de auditoría y, si hace falta, reautenticación. Frente a “añadir una excepción para el caso de administrador dentro de la API de usuario general”, el límite de la política de autorización se ve más claramente.
flowchart LR
accTitle: Cómo evitar BOLA
accDescr: No usar el mid de la solicitud, sino determinar o verificar el destino a partir del sujeto del JWT
A[Usuario] -->|"GET /users/me + JWT"| B[API]
B -->|"Obtiene el sub del JWT"| C{"¿Si hay mid,<br/>coincide con sub?"}
C -->|"Coincide"| D[Devuelve los propios datos]
C -->|"No coincide"| E[Rechazo 403]
B -->|"Sin mid"| F[Busca en la BD por el sub del JWT]
Figura 6: Cómo evitar BOLA. No recibir mid, o cotejarlo con el sujeto del JWT.
Distinguir autenticación y autorización en una frase
Tanto en el examen como en la práctica, ayuda la siguiente distinción.
- Autenticación: quién es
- Autorización: qué le está permitido hacer a esa persona
Que la verificación de la firma del JWT tuviera éxito solo llega hasta el punto de “se puede confiar en que este token representa a este sujeto”. Que “ese sujeto pueda leer a user02” debe confirmarse por separado.
8. Pregunta 2(4) — status=paid: una falla de autorización a nivel de propiedad
La especificación de la API de usuario define los siguientes parámetros para la actualización.
mid ID de usuario
name Nombre
age Edad
Sin embargo, el diagnosticador añadió el siguiente valor, que no está en la especificación.
status=paid
Con esto, el estado de un usuario gratuito cambió a usuario de pago.
Según el enunciado, el servicio L no validaba los parámetros recibidos y los pasaba todos tal cual al módulo común P. P estaba construido de modo que podía actualizar la base de datos directamente.
La respuesta del espacio en blanco c es el módulo común P.
flowchart TB
accTitle: Mass Assignment
accDescr: Se añade un status=paid fuera de especificación y se refleja por completo en el objeto interno
A["Especificación de la API<br/>mid / name / age"] -->|"El atacante añade status=paid"| B[Cuerpo de la solicitud]
B -->|"Vinculación automática"| C[Módulo común P]
C -->|"Guardado en la BD"| D[El estado de facturación cambia a paid]
Figura 7: Mass Assignment. Una propiedad fuera de especificación se refleja por completo en el objeto interno.
Diferencia con BOLA
La sustitución de mid del capítulo anterior y la adición de status de esta sección se parecen, pero difieren en el nivel de protección.
| Vulnerabilidad | Qué cambia el atacante | Qué debería haberse confirmado |
|---|---|---|
Sustitución de mid |
El objeto de destino | Si este usuario puede acceder a este registro de usuario |
Adición de status |
Una propiedad dentro del objeto | Si este usuario puede modificar este elemento |
En el OWASP API Security Top 10 2023, esto último se trata como Broken Object Property Level Authorization, e incluye dentro de esa categoría el antiguo Mass Assignment8.
“Volcar el JSON directamente en la entidad” es peligroso
La implementación vulnerable, de forma conceptual, es como sigue.
entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)
Aunque la pantalla solo tenga campos de entrada para name y age, el atacante puede construir directamente la solicitud HTTP. Los elementos que no están en la interfaz no son un límite de seguridad.
La implementación segura declara explícitamente los elementos que se pueden actualizar.
input = parseExactSchema(body, fields = ["name", "age"])
entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age = input.age
repository.save(entity)
Aquí son importantes dos cosas.
- Colocar en el tipo de entrada de actualización únicamente los elementos que el usuario puede modificar
- En lugar de ignorar en silencio los elementos desconocidos fuera de especificación, rechazarlos como error siempre que sea posible
Si se ignoran en silencio los elementos desconocidos, se oculta que el ataque falló, pero se pasan por alto errores de implementación del cliente o indicios de ataque. Salvo que haya razones de compatibilidad, rechazar con un esquema estricto facilita la investigación.
flowchart TB
accTitle: Autorización a nivel de propiedad
accDescr: El DTO de actualización solo contiene una lista de propiedades permitidas y rechaza las propiedades desconocidas
subgraph "DTO de actualización(lista de permitidos)"
D1["name"]
D2["age"]
end
A[Cuerpo de la solicitud] -->|"Validación de esquema"| B{"¿Solo contiene<br/>propiedades permitidas?"}
B -->|"Sí"| C[Actualiza name/age de la entidad]
B -->|"No"| D[Devuelve un error]
E["Servicio de pago<br/>notificación verificada"] -->|"Ruta dedicada"| F[Actualiza status=paid]
Figura 8: Autorización a nivel de propiedad. Se limitan mediante una lista de permitidos los elementos actualizables, y el estado de facturación se cambia por una vía distinta.
status solo debe cambiar a partir del resultado del pago
status=paid no forma parte del perfil del usuario. Es un estado que se deriva del hecho, del lado del servidor, de que el pago se completó con éxito.
Actualización del perfil del usuario
-> solo se puede cambiar name / age
Notificación verificada del servicio de pago
-> coteja paymentId
-> evita el procesamiento duplicado
-> cambia status a paid
Aunque se guarde en la misma columna de la base de datos, el permiso y la vía para cambiarla son distintos. Si se usa la entidad interna directamente como tipo de entrada de una API externa, este límite desaparece.
9. Pregunta 2(5) — La defensa contra la fuerza bruta consiste en mantener el número de fallos como estado
Frente a la fuerza bruta sobre el código de 4 dígitos, se pide responder, en no más de 30 caracteres, qué proceso va en el espacio en blanco d de la tabla 5. El umbral es 10.
El ejemplo de respuesta es el siguiente.
Proceso que bloquea la cuenta cuando el número de fallos consecutivos supera el umbral
Esto no contradice el carácter stateless de la pregunta 1. No mantener como sesión del servidor el estado de la conversación de cada llamada a la API es una cosa, y persistir el número de fallos necesario para la decisión de seguridad es otra distinta.
flowchart LR
accTitle: Con y sin límite de intentos
accDescr: Sin límite se vulnera en promedio a los 500 segundos, pero un límite de fallos reduce la velocidad del ataque
subgraph "Sin límite"
A1[10 intentos por segundo] -->|"Unos 500 s"| B1[Autenticación exitosa]
end
subgraph "Con límite"
A2[Bloqueo tras 10 fallos] -->|"Reduce drásticamente la velocidad del ataque"| B2[Bloqueo de cuenta]
C2[Retraso escalonado] --> B2
end
Figura 10: Con y sin límite de intentos. El límite de fallos permite detener la fuerza bruta de forma práctica.
En la práctica, no basta con “solo bloqueo permanente”
El límite de intentos por cuenta es necesario, pero si el atacante conoce el ID de usuario de otra persona, puede fallar 10 veces a propósito para excluir al usuario legítimo. Por eso, en la práctica se combina lo siguiente.
| Control | Función |
|---|---|
| Número de fallos por cuenta | Detiene la fuerza bruta sobre una sola cuenta |
| Retraso escalonado | Tolera los errores de entrada del usuario legítimo mientras reduce la velocidad del ataque |
| Control por IP de origen, dispositivo, ASN, etc. | Frena los ataques que prueban pocas veces contra muchas cuentas |
| Evaluación basada en riesgo | Restringe con más fuerza regiones, dispositivos o velocidades inusuales |
| Notificación al usuario | Permite darse cuenta de un ataque o de un error de operación |
| Procedimiento de recuperación seguro | Evita que la vía para desbloquear se convierta en una vía de ataque |
Además, al reenviar el código no se debe reiniciar a cero el número de fallos. De lo contrario, el atacante podría restaurar su cupo de intentos cada vez que llame a la API de reenvío. La versión vigente de NIST SP 800-63B también exige no reiniciar el número de fallos aunque se genere un nuevo secreto de autenticación4.
Hacer que el código de autenticación se pueda usar solo una vez
El enunciado se centra en el periodo de validez, pero en la práctica también hace falta lo siguiente.
- Invalidar de inmediato un código que ha tenido éxito
- Rechazar la reutilización del mismo código
- No dejar el propio código en los registros
- Que el resultado de la comparación del código no permita deducir si el usuario existe
- Poner también un límite de intentos en la API de envío del código
Usando un secreto corto, no se puede confiar la seguridad solo a la generación aleatoria.
flowchart TB
accTitle: Medidas para el código de autenticación
accDescr: Además del número de dígitos y el tiempo de caducidad, se protege con límite de intentos, rechazo de reutilización, notificaciones, etc.
A[Código de autenticación] --> B[Aumentar el número de dígitos]
A --> C[Acortar el periodo de validez]
A --> D[Límite de intentos]
A --> E[Invalidar tras el éxito]
A --> F[No reiniciar los fallos al reenviar]
A --> G[No dejar el código en los registros]
A --> H[Control por origen de la solicitud]
Figura 11: Medidas para el código de autenticación. No solo el número de dígitos y el tiempo de caducidad, sino también el control de intentos y la operación.
10. Distinguir las cuatro partes de la pregunta 2 en una sola tabla
Ordenamos, según el valor que controló el atacante, los puntos de la pregunta 2 que se prestan a confusión.
| Ataque | Valor que cambió el atacante | Lugar en el que no debía confiarse | Contramedida de fondo |
|---|---|---|---|
| Alteración del JWT | El alg de la cabecera del JWT, el ID de usuario del payload |
El algoritmo de verificación que declara el propio token | Fijar en el servidor los algoritmos permitidos |
| Obtención de información de otra persona | El mid de la solicitud |
El ID de destino indicado por el cliente | Cotejarlo con el sujeto del JWT, o determinar el ID de destino a partir del JWT |
| Cambio a usuario de pago | El status fuera de especificación |
Todas las propiedades vinculadas automáticamente | Convertir las propiedades actualizables en una lista de permitidos |
| Vulneración del código de 4 dígitos | El candidato de otp |
Los intentos de autenticación sin límite | Introducir límite de fallos, retraso y evaluación de riesgo |
Es importante no agrupar todo bajo “validar el valor de entrada”.
alges una política de procesamiento criptográficomides autorización a nivel de objetostatuses autorización a nivel de propiedadotpes resistencia frente a la adivinación en línea
Aunque estén dentro de la misma solicitud HTTP, la razón para protegerlos es distinta.
11. Pregunta 3(1) — Confirmar la ejecución remota de código sin causar daños
Después de iniciar el servicio, se hace pública una vulnerabilidad grave V en la biblioteca H de código abierto, ampliamente utilizada. El enunciado describe el siguiente flujo.
- El atacante envía en una cabecera HTTP una cadena que contiene un JNDI Lookup
- El servidor atacado emite ese valor a los registros
- La biblioteca vulnerable evalúa el JNDI Lookup y consulta a un servidor LDAP del atacante
- La respuesta LDAP devuelve la URL de un servidor HTTP del atacante
- El servidor atacado obtiene un archivo de clase y ejecuta un comando
Esto se puede leer como un ataque de tipo Log4Shell (CVE-2021-44228) con el nombre propio oculto. En la propia explicación de Apache también se describe como una vulnerabilidad en la que, si el atacante controla el mensaje de registro o un parámetro, puede ejecutarse código arbitrario cargado desde un servidor LDAP9.
flowchart LR
accTitle: Flujo de verificación de una vulnerabilidad de tipo Log4Shell
accDescr: Confirma con una devolución de llamada inofensiva si la cadena desde JNDI hasta la ejecución remota de código llega a completarse
A[Atacante] -->|"Inyecta en x-api-version<br/>jndi:ldap://..."| B[Servidor vulnerable]
B --> C[Procesamiento de registros]
C -->|"JNDI Lookup"| D[Servidor LDAP malicioso]
D -->|"Respuesta con URL HTTP"| E["Servidor HTTP malicioso<br/>index.html"]
E -->|"Registra el GET"| F["Servidor de pruebas<br/>registro de accesos"]
F -->|"Confirma el alcance"| G[Se confirma la vulnerabilidad]
Figura 12: Flujo de verificación de una vulnerabilidad de tipo Log4Shell. En lugar de una instrucción destructiva, se confirma el alcance mediante el registro de un acceso HTTP.
El código de verificación solo genera un acceso HTTP inofensivo
La empresa G ejecuta un código de verificación que no afecta al sistema, para confirmar si la vulnerabilidad V se puede explotar desde el exterior. La única instrucción que ejecuta el código de verificación es obtener el index.html del servidor de pruebas.
La pregunta 3(1) plantea qué hay que implementar en el servidor de pruebas para poder confirmar que se ejecutó un comando.
El ejemplo de respuesta es el siguiente.
Un mecanismo que registra y permite confirmar los accesos al index.html del servidor de pruebas
Si en el registro de accesos del servidor web queda constancia de un GET procedente del servidor atacado, se puede confirmar que se completó al menos la siguiente cadena.
Solicitud HTTP externa
-> Procesamiento de registros
-> JNDI Lookup
-> Respuesta LDAP
-> Obtención de la clase
-> Ejecución del comando de verificación
-> Acceso HTTP al servidor de pruebas
Por qué no basta con “mostrar texto en pantalla”
El objetivo del ataque es el servidor. No necesariamente aparece ningún cambio en la pantalla del navegador del usuario. Además, aunque exista la vulnerabilidad, la comunicación saliente intermedia puede quedar bloqueada por un cortafuegos.
Si se registra el acceso en el lado del servidor de pruebas, se obtiene una evidencia observable de que el servidor atacado llegó hasta el exterior.
Al realizar este mismo tipo de verificación en la práctica, hay que respetar siempre lo siguiente.
- Obtener la autorización explícita del propietario del sistema objetivo
- Usar un método de verificación sin impacto en producción, o con un impacto tolerable
- No usar instrucciones destructivas como escritura, borrado o cambios de configuración
- Gestionar uno mismo el dominio o el servidor usados para la verificación
- Registrar la hora de la verificación, el origen, el objetivo y la devolución de llamada esperada
- Retirar, después de la verificación, los servidores LDAP y HTTP temporales y las credenciales usadas
“Confirmar la ejecución remota de código” y “ejecutar código arbitrario peligroso” no son lo mismo. Hay que limitarse al mínimo efecto secundario que cumpla el objetivo.
12. Pregunta 3(2)(3) — El WAF inspecciona las cabeceras HTTP
El WAF del servicio N permite elegir como objeto de inspección GET, POST, PUT, ANY, Header, COOKIE y Multipart.
El código de ataque se coloca en el valor de una cabecera HTTP llamada x-api-version. Por lo tanto, los espacios en blanco e y f de la tabla 6 son ambos Header.
Haga coincidir directamente el lugar indicado en el enunciado con el objeto de inspección del WAF
Aquí, más que conocimiento general, se trata de leer el flujo de datos del enunciado.
Ubicación de la cadena de ataque:
cabecera x-api-version
|
v
Objeto de inspección del WAF:
Header
No es un parámetro GET ni el cuerpo de un POST. En lugar de mirar la lista de funciones del WAF y elegir “ANY porque parece un ataque”, se responde con el lugar donde el atacante colocó el valor según el enunciado.
Cómo responder a la alternancia de mayúsculas y minúsculas
La primera propuesta, de forma conceptual, era la siguiente regla.
Header \Wjndi\W bloquear
Header \Wldap\W bloquear
Sin embargo, alternando mayúsculas y minúsculas como en jNdI se puede eludir un patrón simple que solo use minúsculas.
El ejemplo de respuesta de la pregunta 3(3) es cualquiera de los siguientes.
\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W
En el cuadernillo del examen, la barra invertida puede verse, según la tipografía del entorno japonés, como el símbolo del yen, pero como expresión regular es \W. \W coincide con cualquier carácter que no sea alfanumérico ni guion bajo. En la sintaxis del JNDI Lookup, antes y después de jndi aparecen caracteres no alfanuméricos como ${ o :, por eso se incluyen en el patrón.
Con el mismo criterio, el lado de ldap también se puede hacer indiferente a mayúsculas y minúsculas.
\W[lL][dD][aA][pP]\W
No considere esta expresión regular como una “defensa completa contra Log4Shell”
El examen pide responder con la expresión regular que corresponde al método de evasión mostrado en el enunciado. En ataques reales puede haber variantes difíciles de cubrir solo con firmas: división de la cadena, otros Lookup, codificación, otros protocolos, etc.
Por lo tanto, en la práctica el posicionamiento es el siguiente.
- Detener de forma provisional con el WAF los patrones de ataque ya conocidos
- Investigar si realmente está incluida la biblioteca afectada
- Restringir la comunicación saliente innecesaria hacia LDAP, RMI, HTTP
- Actualizar a la versión corregida
- Aunque ya se haya actualizado, revisar los registros para investigar si hubo compromiso
El WAF es una capa que gana tiempo hasta que sale la versión corregida.
flowchart TB
accTitle: Posición del WAF
accDescr: El WAF es una capa de mitigación provisional, la solución definitiva es actualizar a la versión corregida de la biblioteca
A[Publicación de una vulnerabilidad grave] --> B[Confirmación del impacto]
B --> C[Mitigación provisional]
C -->|"Reglas de WAF<br/>detección/bloqueo"| D[Detiene temporalmente el patrón de ataque]
C -->|"Restricción de comunicación saliente"| E[Cierra la vía de explotación]
D --> F[Actualización a la biblioteca corregida]
E --> F
F --> G[Verificación posterior y prevención de recurrencia]
Figura 13: Posición del WAF. El WAF gana tiempo hasta que sale la versión corregida; la solución definitiva es la actualización.
13. Pregunta 3(4) — Por qué usar primero el modo “detección”
Sobre la nueva regla del WAF, el Sr. Z, especialista en seguridad de la información registrado, aconseja que, durante un periodo determinado tras el inicio de la operación en producción, el comportamiento sea “detección” en lugar de “bloqueo”.
La pregunta plantea, en no más de 25 caracteres cada uno, la ventaja de usar la detección y lo que hay que hacer para minimizar el daño.
El ejemplo de respuesta es el siguiente.
| Elemento | Punto clave de la respuesta |
|---|---|
| Ventaja | Se puede evitar el bloqueo por falsos positivos |
| Qué hay que hacer | Al recibir una alerta, examinar si es un ataque |
El modo de detección no es un “modo de no hacer nada”
En el modo de detección, se deja pasar la comunicación que coincide con la regla, pero se registra y se genera una alerta. Aunque la palabra jndi o ldap aparezca por casualidad dentro de una llamada normal a la API, no se detiene de inmediato el negocio.
A cambio, del lado de la operación hace falta lo siguiente.
Recepción de la alerta
|
v
Revisar la solicitud correspondiente
|
+-- comunicación normal -> reducir el alcance de la regla, considerar excepciones
|
+-- ataque -> aislar el objetivo, preservar registros, investigar el impacto, pasar a bloqueo
Si nadie revisa las alertas, el modo de detección no tiene efecto defensivo. La detección va siempre unida a una operación que observa y decide.
El flujo para pasar de detección a bloqueo
El procedimiento habitual de introducción es el siguiente.
- Aplicar el modo de detección al tráfico real
- Clasificar los falsos positivos y los verdaderos positivos
- Ajustar cabeceras, rutas, API, límites de caracteres objetivo, etc.
- Confirmar que el impacto sobre la comunicación normal es tolerable
- Pasar al modo de bloqueo
- Monitorear el número de bloqueos y el impacto en el negocio
Sin embargo, esto es el principio en tiempos normales. Si la vulnerabilidad es grave, ya está siendo explotada de hecho y no hay alternativa, se puede decidir bloquear desde el principio, considerando que el daño de la vulneración pesa más que el de una interrupción por falso positivo. En la situación del examen, para confirmar que el servicio se puede seguir usando como hasta ahora, primero se elige la detección.
flowchart LR
accTitle: Del modo de detección al modo de bloqueo en el WAF
accDescr: Se observan las alertas en modo de detección, se ajustan los falsos positivos y después se pasa al modo de bloqueo
A[Modo de detección] -->|"Tráfico real"| B[Se genera una alerta]
B --> C{"¿Ataque o<br/>falso positivo?"}
C -->|"Falso positivo"| D[Ajusta las reglas]
D --> A
C -->|"Ataque"| E[Pasa a modo de bloqueo]
E --> F[Monitorea el número de bloqueos y el impacto en el negocio]
Figura 14: De la detección al bloqueo. Primero se observa y se ajusta, y una vez confirmado que el impacto es tolerable, se pasa al bloqueo.
14. El WAF es una medida provisional; la actualización es la solución definitiva
En el enunciado, el sitio oficial de la biblioteca H todavía no tenía ni versión corregida ni medida provisional, y la regla exhaustiva de WAF del proveedor de la nube podía tardar hasta 72 horas. Por eso la empresa G confirma por sí misma el impacto y detiene de forma provisional, aunque sea solo, los patrones ya conocidos.
Este orden es la forma básica de la respuesta a incidentes.
| Etapa | Objetivo | Respuesta en este problema |
|---|---|---|
| Confirmación del impacto | Decidir si la propia organización está realmente en riesgo | Confirmar con una devolución de llamada inofensiva si es posible explotarla desde el exterior |
| Mitigación provisional | Ganar tiempo hasta la corrección | Reglas de WAF, detección/bloqueo, restricción de comunicación saliente |
| Corrección de fondo | Eliminar la causa vulnerable | Actualizar a la biblioteca corregida |
| Verificación posterior | Investigar si ya hubo explotación | Investigar los registros de WAF, aplicación, DNS, proxy, etc. |
| Prevención de recurrencia | Acelerar la próxima decisión | Inventario de dependencias, SBOM, procedimiento de actualización, vías de contacto |
“No saber si se está usando” es el mayor factor de retraso
Según el enunciado, cuando la empresa G pregunta a la empresa F si usa la biblioteca H, se le responde que hace falta un análisis detallado de la configuración y que la respuesta tardará.
En la práctica, si se empieza a buscar el archivo JAR solo después de que se hace pública una vulnerabilidad grave, la respuesta llega tarde. Como mínimo, conviene tener de antemano lo siguiente.
- Un inventario de dependencias directas y transitivas
- Los componentes y versiones realmente incluidos en cada artefacto distribuido
- En qué servicios, contenedores o dispositivos están desplegados
- Un procedimiento para actualizar la biblioteca dependiente y reconstruir/redistribuir
- Una vía de contacto para aprobar cambios urgentes
- Los destinos permitidos de la comunicación saliente y el impacto de bloquearlos
- Dónde se guardan los registros y cómo buscarlos
El SBOM no es un fin en sí mismo. Es un índice para responder en poco tiempo a “a qué sistemas en ejecución afecta esta vulnerabilidad”.
No dar por terminada la investigación con solo actualizar
Es posible que ya se haya sufrido un ataque antes o después de la publicación de la vulnerabilidad. Aunque se actualice a la versión corregida y se detenga la explotación futura, no desaparecen las credenciales ya comprometidas ni las puertas traseras ya instaladas.
En un caso de tipo Log4Shell, conviene investigar al menos lo siguiente.
- Solicitudes HTTP con cadenas sospechosas que indiquen JNDI o LDAP
- Comunicación desde el servidor de aplicación hacia LDAP, RMI o HTTP externos
- Inicio de procesos hijo distintos de lo habitual
- Creación de archivos JAR, class, scripts o ejecutables sospechosos
- Acceso a credenciales de la nube o a variables de entorno
- Cambios de autenticación, de permisos o envíos externos antes y después de la actualización
Es importante no dar por sentado que “no hubo ataque” solo con los registros del WAF. Existen vías internas que no pasan por el WAF, y registros que no se conservaron en el pasado.
15. Una forma de leer el problema que facilita obtener puntos en el examen
Este problema, más que un problema de conocimientos, es un problema de leer la diferencia entre la especificación y la implementación.
15.1 Separar la “especificación” y la “implementación” de la tabla
En el problema de status, un valor que no está en la especificación de la API sí pasa en la implementación.
Especificación:
mid / name / age
Implementación:
Envía todos los parámetros recibidos a P
Si se ve esta diferencia, se entiende que el espacio en blanco c es el módulo común P.
15.2 Señalar los valores que el atacante modificó
Los valores cambiados en cada ataque son los siguientes.
- El
algde la cabecera del JWT - El ID de usuario del payload del JWT
- El
middel parámetro de la API - El
statusfuera de especificación - El
otpde la API de autenticación - La cabecera HTTP
x-api-version
Casi todas las preguntas preguntan, en esencia, “dónde debería verificarse ese valor”.
15.3 Redactar la respuesta con los términos del propio enunciado
En la práctica se puede decir “BOLA”, “Mass Assignment” o “rate limiting”. Pero lo que pide la pregunta es un proceso concreto ajustado a la estructura del enunciado.
Mal ejemplo:
Realizar la autorización de forma adecuada.
Buen ejemplo:
Verificar si el ID de usuario contenido en el JWT coincide con el valor de mid.
Mal ejemplo:
Aplicar una contramedida contra la fuerza bruta.
Buen ejemplo:
Bloquear la cuenta cuando el número de fallos consecutivos supera el umbral.
Con solo conocer el nombre abstracto no se obtiene una respuesta que se pueda puntuar dentro del límite de caracteres.
15.4 El WAF: rastrear “dónde entró” el ataque
El objeto de inspección del WAF no se deduce del tipo de ataque, sino que se decide a partir del lugar donde se almacena la cadena de ataque.
Se colocó en la cabecera x-api-version
↓
El objeto de inspección es Header
Que la tasa de aciertos de la pregunta 3(1) fuera algo baja en el comentario de calificación también se debe, en parte, a respuestas que no encajaban con el flujo de ataque de la figura 6. Solo con reescribir el procedimiento de ataque en forma de flechas, ya se ve con más claridad qué hay que observar.
16. Lista de verificación para revisiones de API en la práctica
Esta es una lista de verificación para llevar este problema a las revisiones de diseño y de código reales.
Verificación de JWT
- El algoritmo de firma permitido está fijado en la configuración del servidor
- Se rechaza
noneo cualquier algoritmo no previsto - Se verifican, según el uso, la firma,
iss,aud,expynbf - No se confunden el token de identidad, el token de acceso y el token de actualización
- Existe un procedimiento de rotación de claves y de revocación
- No se colocan en el payload del JWT datos que deban mantenerse en secreto
Autorización a nivel de objeto
- Al cambiar el ID dentro de la solicitud, no se puede llegar a los datos de otra persona
- Se autoriza en el listado, el detalle, la actualización, el borrado y la descarga, en todos los casos
- La autorización se realiza en una capa común que llega hasta los datos, no en la pantalla
- Para las API de uso propio, se ha considerado si el ID de destino se puede derivar del token
- Las operaciones de administrador se separan en política y API de las de un usuario general
Autorización a nivel de propiedad
- El tipo de entrada externo y la entidad de la base de datos están separados
- Los elementos actualizables se enumeran mediante una lista de permitidos
- Las propiedades fuera de especificación se rechazan o se auditan
- Estados como permisos, facturación, aprobación o propietario no se pueden cambiar desde la entrada del usuario
- La respuesta tampoco incluye propiedades confidenciales innecesarias
Intentos de autenticación
- Existe un límite de fallos por cuenta
- Existe un retraso escalonado o un control por origen de la solicitud
- El número de fallos no se reinicia al reemitir el código
- El código de autenticación se puede usar solo una vez
- No se deja el código de autenticación ni la contraseña en los registros
- El procedimiento de desbloqueo o recuperación no se convierte en otra vía de autenticación débil
Vulnerabilidades críticas en bibliotecas dependientes
- Se pueden vincular los servicios en ejecución con las versiones dependientes
- Existe un procedimiento para verificar el impacto de forma inofensiva
- Se pueden aplicar medidas provisionales como el WAF o la restricción de comunicación saliente
- Existe una operación en la que un responsable revisa las alertas de detección
- Existe una vía de publicación urgente para actualizar a la versión corregida
- Se investiga en los registros, antes de la actualización, si hubo posible explotación
17. La doble cara de los componentes comunes que revela este problema
En este problema aparecen dos componentes comunes: la biblioteca de gestión de JWT Q y el módulo común P.
Los componentes comunes tienen grandes ventajas.
- Corrigiendo un solo lugar, se puede propagar la corrección a todas las API que lo usan
- Se evita duplicar en cada función la implementación de autorización o verificación
- Se puede concentrar el objeto de las pruebas
- Se puede unificar el formato de registros y auditoría
Por otro lado, los errores también se propagan a todo el conjunto.
- Si la biblioteca Q acepta
alg=none, todas las API que usan JWT quedan en riesgo - Si el módulo común P acepta cualquier
midostatus, tanto GET como PUT quedan en riesgo - Si la biblioteca vulnerable H se usa en la base, las múltiples rutas que emiten cabeceras HTTP a los registros se convierten en superficie de ataque
Por lo tanto, lo que conviene poner en común no es simplemente el acceso a los datos. Hace falta poner en común los invariantes de seguridad y verificar con rigor, de forma aislada, ese componente común.
Por ejemplo, el contrato de P se puede definir así.
P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})
Es más seguro no exponer directamente a los llamadores generales una API de bajo nivel como la siguiente.
P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)
Esta última solo hace falta para vías limitadas, como el procesamiento administrativo. Si se reparte a todas las API la libertad de bajo nivel, el diseño acaba dependiendo de que cada llamador la use correctamente todas las veces.
18. Resumen
La pregunta 1 de la tarde del examen de primavera de Reiwa 6 (2024) es un problema que hace leer, separados uno a uno, los puntos de la seguridad de API.
Usar JWT no significa que la autenticación sea segura. Si se deja que el atacante elija el algoritmo de firma, se puede reescribir el ID de usuario.
Que la firma del JWT sea correcta no significa que la autorización sea correcta. Si se confía en el mid de la solicitud, un usuario legítimo puede acceder a la información de otra persona.
Poder actualizar el propio objeto no significa que se puedan cambiar todas las propiedades. Si se vincula automáticamente un estado interno como status, se pueden reescribir permisos o el estado de facturación.
Que el código de autenticación tenga un periodo de validez no significa que resista la fuerza bruta. Hay que calcular el número de candidatos y la velocidad de los intentos, y limitar el número de fallos.
Poner reglas en el WAF no significa haber corregido la vulnerabilidad. Se gana tiempo con la detección y el bloqueo, se confirma el impacto y, al final, se actualiza la biblioteca.
flowchart TB
accTitle: Tabla de correspondencia entre vulnerabilidades y contramedidas
accDescr: Asocia cada vulnerabilidad con su límite de confianza y su contramedida
A[Alteración del JWT] -->|"Verificación del token"| B[Fijar los algoritmos permitidos]
C[Sustitución del mid] -->|"Autorización a nivel de objeto"| D[Verificar con el sujeto del JWT / mid innecesario]
E[status=paid] -->|"Autorización a nivel de propiedad"| F[Convertir el DTO de actualización en lista de permitidos]
G[Fuerza bruta del código de 4 dígitos] -->|"Control de intentos de autenticación"| H[Límite de fallos / retraso]
I[Vulnerabilidad de tipo Log4Shell] -->|"De la entrada a la ejecución"| J[Actualizar biblioteca / WAF]
Figura 15: Tabla de correspondencia entre vulnerabilidades y contramedidas. La contramedida se separa según el límite roto.
El principio que recorre todo este problema es uno solo.
No convertir el éxito de una comprobación anterior en motivo para omitir el siguiente límite de confianza.
En artículos anteriores de esta serie se explican el XSS almacenado de la pregunta 1 de la tarde de otoño de Reiwa 5 y la sustracción de información desde el Wi-Fi para visitantes de la pregunta 2 de la tarde de otoño de Reiwa 5. Para una visión de conjunto de los puntos a verificar en un sitio web completo, consulte también Usar «Cómo crear un sitio web seguro» de IPA como lista de verificación.
flowchart TB
accTitle: Resumen final
accDescr: Muestra que el éxito de una verificación anterior no exime de comprobar el siguiente límite de confianza
A[Autenticación exitosa] --> B[Verificación de la firma del JWT]
B --> C[Autorización a nivel de objeto]
C --> D[Autorización a nivel de propiedad]
D --> E[Límite de intentos]
E --> F[Límite entre la entrada y la ejecución]
F --> G[WAF / actualización de biblioteca]
Figura 16: Resumen final. Los límites de confianza se comprueban por etapas, y ninguno debe omitirse.
Referencias
-
IPA, Examen de Especialista en Seguridad de la Información Registrado, primavera de Reiwa 6 (2024), tarde, cuadernillo de preguntas. El enunciado que trata este artículo. ↩
-
IPA, Examen de Especialista en Seguridad de la Información Registrado, primavera de Reiwa 6 (2024), tarde, ejemplo de respuestas. El ejemplo de respuesta oficial de cada pregunta. ↩
-
IPA, Examen de Especialista en Seguridad de la Información Registrado, primavera de Reiwa 6 (2024), tarde, comentario de calificación. Explica la tasa de aciertos y las tendencias de las respuestas erróneas. ↩
-
NIST, SP 800-63B: Authentication and Authenticator Management. Indica el número de dígitos de los secretos de corta duración, el límite de intentos, el número de fallos al reemitir y la recomendación de no usar el correo electrónico para autenticación fuera de banda. ↩ ↩2
-
RFC Editor, RFC 7519: JSON Web Token (JWT). La especificación del Unsecured JWT y del JWT con
alg=none. ↩ -
RFC Editor, RFC 8725: JSON Web Token Best Current Practices. El BCP que define fijar los algoritmos permitidos y verificar el emisor, el sujeto y la audience, entre otros. ↩
-
OWASP, API1:2023 Broken Object Level Authorization. Explica la necesidad de confirmar la autorización para cada ID de objeto indicado por el usuario. ↩
-
OWASP, API3:2023 Broken Object Property Level Authorization. Explica la falla de autorización a nivel de propiedad, incluido el Mass Assignment, y sus contramedidas. ↩
-
Apache Logging Services, Security. Explica el impacto de CVE-2021-44228, la ejecución de código a través de JNDI y LDAP, y la versión corregida. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Análisis del examen de Especialista en Seguridad de la Información Registrado, otoño de Reiwa 5, tarde, pregunta 1 — el XSS almacenado donde 16 reseñas se muestran como solo 2
A partir de la pregunta 1 de la tarde del examen de Especialista en Seguridad de la Información Registrado, otoño de Reiwa 5, se explica ...
Especialista en Seguridad de la Información, otoño de Reiwa 5, pregunta 2 de la tarde — Archivos sustraídos por el Wi-Fi de invitados
A partir de la pregunta 2 de la tarde de R5 otoño, explica cómo se filtran archivos por el Wi-Fi de invitados pese a bloquear el USB, y r...
Las 10 principales amenazas de seguridad de la información 2026 — cómo leer el ranking y qué deben abordar realmente las pymes
En el «Top 10 de amenazas de seguridad de la información 2026» de IPA, el ransomware repite en el 1.º puesto por 11.º año consecutivo, la...
Lo que también debe saber quien encarga un sitio web — cómo usar la guía «Cómo crear un sitio web seguro» de la IPA como checklist
¿Con qué criterio revisar la seguridad de su sitio web? Explicamos las 11 vulnerabilidades de la guía IPA «Cómo crear un sitio web seguro...
Medidas de seguridad para pymes, por dónde empezar — Cómo recorrer la versión 4.0 de la «Guía de medidas de seguridad de la información para pymes» del IPA
¿Por dónde empezar la seguridad en una pyme? La guía v4.0 del IPA explica los seis principios básicos, la autoevaluación de 5 minutos y S...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de sitios web
En las API para socios registrados y en la integración con aplicaciones móviles, la verificación de JWT, la autorización a nivel de objeto y la limitación de las propiedades actualizables inciden directamente en la seguridad del sistema web.
Consultoría técnica y revisión de diseño
Detectar, mediante una revisión de diseño, las lagunas de autorización en las API existentes, el alcance del impacto de las bibliotecas dependientes y las reglas provisionales de WAF es precisamente el tipo de trabajo que cubre nuestra consultoría técnica.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Por qué se puede suplantar a otra persona aunque la verificación de la firma del JWT haya tenido éxito?
- En este problema, la biblioteca de gestión de JWT aceptaba el alg de la cabecera del JWT tal como lo indicaba el atacante, y trataba un JWT con alg=none como válido sin firma. Por lo tanto, incluso reescribiendo el ID de usuario del payload, la verificación se completaba con éxito. La respuesta del examen es que el objeto a verificar debe ser el alg de la cabecera del JWT, comprobando que su valor no sea NONE. Sin embargo, en la práctica no basta con rechazar solo NONE. Hay que fijar en la configuración del servidor los algoritmos permitidos, por ejemplo RS256, sin usar directamente el algoritmo declarado por el propio token para la selección. Además, según el uso, conviene verificar también el issuer, la audience, la fecha de expiración y el subject.
- ¿Basta con comparar el ID de usuario contenido en el JWT con el mid de la solicitud como medida de autorización?
- Como respuesta a este problema, es suficiente. Si el módulo común P verifica que el ID de usuario incluido en el JWT coincide con el mid, se puede detener el ataque que especifica el mid de otra persona. Sin embargo, para una API que solo maneja la información del propio usuario, en la práctica es más seguro no recibir el mid del cliente y determinar el ID de usuario a partir del subject del JWT ya verificado. Por ejemplo, usando GET /users/me o PUT /users/me se reduce la posibilidad de que falte implementar la propia comparación. Las API mediante las que un administrador opera sobre otros usuarios deben separarse en otro endpoint y otra política de autorización.
- ¿Por qué el ataque que añade status no se puede evitar solo con la validación habitual de valores de entrada?
- Aunque se valide la longitud de name o el rango de age, si de entrada se vincula automáticamente un status que ni siquiera debería recibirse y se pasa al objeto interno, no se puede evitar. El problema no es el formato del valor, sino la autorización a nivel de propiedad: si el usuario tiene permiso para modificar esa propiedad. En el tipo de entrada para la actualización solo deben definirse name y age, y las propiedades desconocidas deben rechazarse. El estado de facturación debe cambiarse únicamente a partir de eventos en los que el servidor pueda confiar, como el resultado exitoso del servicio de pago.
- El código de autenticación de 4 dígitos caduca a los 10 minutos: ¿por qué sigue siendo peligroso?
- Porque los candidatos, de 0000 a 9999, solo suman 10.000 combinaciones, y si se pueden probar 10 por segundo, el promedio es de 5.000 intentos, es decir, se acierta en 500 segundos. Como el periodo de validez de 10 minutos son 600 segundos, probando candidatos distintos en secuencia se pueden verificar 6.000 combinaciones dentro de ese periodo. Solo con el tiempo de caducidad no se puede detener la fuerza bruta. Es necesario diseñar juntos el número de candidatos, la velocidad de los intentos y el límite de intentos.
- La contramedida de la pregunta es el bloqueo de la cuenta: ¿basta también en la práctica con un bloqueo inmediato?
- No. En el espacio en blanco del problema se coloca el proceso de bloquear la cuenta cuando el número de fallos consecutivos supera un umbral, pero un bloqueo permanente y fijo por sí solo permite que un atacante provoque una denegación de servicio bloqueando deliberadamente la cuenta de otra persona. En la práctica se combinan el número de fallos por cuenta, un retraso escalonado, la evaluación de riesgo del origen o del dispositivo, notificaciones y procedimientos de recuperación. También es importante no reiniciar a cero el número de fallos al emitir un nuevo código.
- ¿Qué sentido tiene poner el WAF en modo de detección en lugar de bloqueo?
- Que, incluso cuando una cadena normal se clasifica erróneamente como ataque, no se detenga la comunicación de negocio. En la respuesta del problema, el beneficio es que se puede evitar el bloqueo por falsos positivos, y lo que hay que hacer es examinar, al recibir una alerta, si se trata realmente de un ataque. El modo de detección no es una configuración para dejarlo sin más. Se usa como periodo de observación para revisar los registros, eliminar los falsos positivos, ajustar las reglas y pasar después al bloqueo. En una emergencia en la que una vulnerabilidad grave y conocida ya está siendo explotada activamente, también puede ser razonable decidir bloquear primero, comparando ese riesgo con el riesgo de disponibilidad.
- ¿Es Log4j la biblioteca H de este problema?
- El enunciado oculta el nombre del producto, pero la secuencia del ataque —JNDI Lookup, un servidor LDAP, la obtención de una clase desde un servidor HTTP, una cadena incrustada en una cabecera HTTP y un valor base de CVSS v3.1 elevado— es natural leerla como una abstracción de CVE-2021-44228, conocida como Log4Shell. En este artículo se explica esa correspondencia, pero en el examen no hace falta responder con el nombre propio: se puede resolver a partir del procedimiento de ataque dado y de las especificaciones del WAF.
- ¿Qué debería llevarse a la práctica profesional a partir de este problema?
- Que el éxito de la autenticación, que el JWT no haya sido alterado, que se tenga permiso para acceder al objeto de destino y que se tenga permiso para modificar la propiedad de destino son, todas ellas, comprobaciones distintas entre sí. Además, los códigos de autenticación cortos necesitan un límite de intentos, y ante una vulnerabilidad grave de una biblioteca conviene avanzar en paralelo la verificación del impacto, la defensa provisional y la corrección definitiva. Los ejes centrales en la práctica son concentrar la autorización en componentes comunes, convertir el esquema de entrada en una lista de permitidos, fijar en el servidor las condiciones de verificación del JWT, y mantener un conocimiento actualizado de las bibliotecas dependientes que permita actualizarlas.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.