Seguridad de APIs REST:
autenticación, rate limiting y el OWASP API Top 10
Seguridad de aplicaciones web | unfantasmaenelsistema.com
Durante años la superficie de ataque de una aplicación web era su navegador: formularios, cookies, páginas renderizadas en el servidor. Hoy la lógica vive en el cliente —una SPA, una app móvil, un servicio que llama a otro— y todo lo que importa pasa por una API REST. Esa API es la puerta real a los datos, y a menudo está peor protegida que la web que la consume, porque se diseñó pensando en «nuestro propio front» y no en un atacante que habla HTTP directamente contra los endpoints.
El proyecto OWASP mantiene una lista específica para este mundo, el API Security Top 10, porque los fallos que dominan aquí no son los mismos que en la web clásica. La inyección y el XSS ceden el protagonismo a los fallos de autorización objeto a objeto, a la exposición excesiva de datos y a la falta de límites. En este artículo recorremos esa lista, vemos cómo se autentica una API moderna y cómo el rate limiting deja de ser un lujo para convertirse en un control de seguridad.
Qué aprenderás en este artículo
Por qué BOLA/IDOR es el fallo número uno de las APIs y cómo se cierra · Las opciones de autenticación: API keys, OAuth2 client credentials y mTLS · Cómo el rate limiting y las cuotas frenan abuso, fuerza bruta y fuga masiva de datos · Validación de esquema con OpenAPI como barrera de entrada · El OWASP API Security Top 10 (2023) resumido en una tabla: ejemplo y defensa de cada punto
1. BOLA / IDOR: el fallo que encabeza la lista
Broken Object Level Authorization (BOLA), conocido tradicionalmente como IDOR (Insecure Direct Object Reference), es el primer punto del Top 10 y con diferencia el más frecuente. La idea es simple: un endpoint recibe un identificador de recurso —GET /api/invoices/4187— comprueba que quien llama está autenticado, pero no comprueba que ese recurso concreto le pertenezca. Cambiar el 4187 por 4188 devuelve la factura de otro cliente.
Es tan común porque la autorización a nivel de objeto hay que escribirla explícitamente en cada controlador, mientras que la autenticación se resuelve una vez en un middleware global. El código «funciona» en todas las pruebas manuales porque el desarrollador siempre pide sus propios recursos.
- Usar identificadores no predecibles (UUID v4) ayuda contra el escaneo secuencial, pero no es la defensa: un ID filtrado en un log o en otra respuesta sigue siendo utilizable
- La defensa real es una comprobación de propiedad en cada acceso: WHERE id = :id AND owner_id = :usuario_actual, o una capa de autorización centralizada que reciba el recurso y el sujeto
- Aplica igual a objetos anidados: /api/users/me/cards/12 debe verificar que la tarjeta 12 es de me, no solo que exista
2. Autenticación de APIs: keys, OAuth2 y mTLS
Antes de autorizar hay que identificar a quien llama. Según sea una persona a través de una app, un servicio automatizado o un socio externo, el mecanismo cambia.
2.1 API keys
Una cadena secreta larga que el cliente envía en cada petición, normalmente en una cabecera (Authorization: Bearer … o una cabecera propia). Identifican a la aplicación, no al usuario, y no llevan información de permisos por sí mismas. Son cómodas para empezar y para tráfico servidor a servidor, pero tienen puntos débiles: se filtran con facilidad (repositorios, logs, apps móviles descompiladas), no caducan solas y suelen concederse con demasiado alcance. Si se usan, deben poder rotarse, tener permisos mínimos y estar asociadas a un origen o rango de IP cuando sea posible.
2.2 OAuth2 y OpenID Connect
El estándar de facto para APIs que actúan en nombre de un usuario. El cliente no maneja la contraseña: obtiene un token de acceso de vida corta de un servidor de autorización y lo presenta a la API. Para tráfico entre servicios sin usuario, el flujo client credentials entrega un token a partir de un identificador y un secreto de cliente. La API valida el token (firma, exp, aud, iss, scope) en cada petición. Es más maquinaria, pero desacopla la emisión de credenciales del consumo y permite revocar y acotar por scope.
2.3 mTLS (TLS mutuo)
En TLS normal solo el servidor presenta certificado. En mTLS también lo hace el cliente, y la API solo acepta conexiones de certificados que reconoce. Es la opción más fuerte para un número acotado de clientes conocidos (integraciones B2B, microservicios dentro de una malla) porque la identidad va atada al canal y no a un secreto que se pueda copiar y reproducir. A cambio, gestionar el ciclo de vida de los certificados —emisión, rotación, revocación— tiene coste operativo.
| Mecanismo | Identifica a | Caso de uso ideal | Punto débil |
|---|---|---|---|
| API key | La aplicación | Servidor a servidor sencillo, planes de desarrollador | Se filtra fácil, no caduca sola, alcance excesivo |
| OAuth2 (usuario) | El usuario vía cliente | Apps y SPAs que actúan en nombre de alguien | Complejidad; errores al validar el token |
| OAuth2 client credentials | El servicio cliente | Tráfico entre servicios con autorización | Secreto de cliente mal custodiado |
| mTLS | El cliente (certificado) | Integraciones B2B, malla de servicios | Gestión de certificados a escala |
3. Exposición excesiva de datos y de funciones
Dos puntos del Top 10 comparten una misma raíz: la API devuelve o acepta más de lo que debería porque confía en que el cliente «ya filtrará».
3.1 Excessive Data Exposure
El endpoint serializa el objeto entero de base de datos —user con su hash de contraseña, su token de recuperación, sus notas internas— y el front solo muestra el nombre. El dato viaja igualmente en la respuesta y cualquiera que mire el tráfico lo ve. La defensa es definir explícitamente qué campos salen: un DTO o esquema de respuesta por endpoint, nunca un return objeto directo.
3.2 Broken Function Level Authorization
El cliente legítimo nunca llama a DELETE /api/users/99 ni a /api/admin/…, así que esos endpoints se protegen «por oscuridad». Un atacante que lee el JavaScript de la SPA o prueba verbos HTTP encuentra la función administrativa y comprueba que solo exige estar autenticado, no ser admin. Cada endpoint debe declarar el rol o permiso que necesita, y el estado por defecto debe ser denegar.
3.3 Mass Assignment
Si el endpoint de «editar mi perfil» hace user.update(datos_del_body) sin lista blanca, el atacante añade «role»: «admin» o «account_balance»: 999999 al JSON y el ORM lo persiste. Acepta solo los campos que el usuario puede tocar, de forma explícita.
4. Rate limiting y cuotas: un control de seguridad, no solo de capacidad
Unrestricted Resource Consumption es otro punto del Top 10. Sin límites por cliente, una API expone varios problemas a la vez: fuerza bruta contra el login, enumeración de identificadores para explotar un BOLA a escala, extracción masiva de datos (scraping), coste descontrolado en endpoints que llaman a servicios de pago (envío de SMS, IA, terceros) y denegación de servicio con peticiones caras.
4.1 Niveles de límite
- Rate limit de ráfaga: peticiones por segundo/minuto y por clave de cliente (token, API key, IP como último recurso). Suele implementarse con token bucket o ventana deslizante
- Cuota de largo plazo: total diario o mensual por plan; separa al usuario normal del que intenta descargarse toda la base de datos
- Límites específicos más estrictos en endpoints sensibles: login, recuperación de contraseña, generación de informes, cualquier cosa que dispare gasto
- Límites de tamaño: longitud máxima del cuerpo, profundidad de JSON, número de elementos en un array, para frenar payloads diseñados para agotar CPU o memoria
4.2 Cabeceras y comportamiento esperado
Una API educada informa del límite en cada respuesta y responde 429 Too Many Requests al superarlo, con una cabecera Retry-After. Así los clientes legítimos pueden adaptarse en lugar de reintentar en bucle.
# Respuesta con informacion de limite (cabeceras habituales)
HTTP/1.1 200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 12
RateLimit-Reset: 27
# Al superar el limite
HTTP/1.1 429 Too Many Requests
Retry-After: 27
Content-Type: application/json
{ "error": "rate_limited", "message": "Vuelve a intentarlo en 27 segundos" }El límite se aplica por identidad, no solo por IP
Limitar solo por IP castiga a oficinas y operadores con NAT y no frena a un atacante con IPs rotativas · El contador debe ir asociado a la credencial (token, API key, cuenta) y, en endpoints anónimos como el login, combinar IP + identificador de usuario objetivo · Detrás de un proxy o CDN, usa la IP real de una cabecera de confianza que tú controles, no la que llegue sin validar
5. SSRF y gestión de inventario
5.1 Server-Side Request Forgery
Muchas APIs aceptan una URL del usuario para «importar desde», «webhook» o «previsualizar enlace». Si el servidor hace esa petición sin restricciones, el atacante le pide que acceda a http://169.254.169.254/ (metadatos de la nube), a servicios internos o a localhost. Es una categoría propia del Top 10 desde 2023. La defensa: lista blanca de dominios y esquemas, resolver el nombre y bloquear rangos internos y de enlace local antes de conectar, y desactivar las redirecciones automáticas. Lo tratamos a fondo en nuestro artículo dedicado a SSRF.
5.2 Improper Inventory Management
El endpoint viejo que nadie mantiene es el que cae. /api/v1/ sigue en producción después de migrar a v2 y no recibió los últimos parches de autorización. El entorno de staging apunta a datos reales y no exige autenticación. Documentación de la API filtra endpoints internos. Hace falta un inventario vivo: qué versiones están expuestas, cuáles están deprecadas y con fecha de apagado, y qué entornos son accesibles desde fuera.
6. Validación de esquema con OpenAPI
Una especificación OpenAPI (antes Swagger) describe cada endpoint: parámetros, tipos, campos obligatorios, formatos, rangos. Más allá de generar documentación, sirve como contrato que se valida en tiempo de ejecución: un middleware rechaza toda petición que no encaje con el esquema antes de que llegue a la lógica de negocio.
- Rechaza campos desconocidos (additionalProperties: false) — mitiga mass assignment de forma estructural
- Fuerza tipos y formatos (un email es un email, un id es un entero positivo) — reduce la superficie de inyección y de errores de parseo
- Limita longitudes y tamaños de array desde el propio contrato
- Permite comparar lo que la API realmente expone con lo que dice exponer (útil contra el punto de inventario)
# Fragmento de OpenAPI 3.1: el cuerpo de 'crear pedido' como contrato estricto
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false # nada fuera de lo declarado
required: [producto_id, cantidad]
properties:
producto_id: { type: integer, minimum: 1 }
cantidad: { type: integer, minimum: 1, maximum: 100 }
nota: { type: string, maxLength: 280 }7. El OWASP API Security Top 10 (2023) de un vistazo
| # | Categoría | Ejemplo | Defensa principal |
|---|---|---|---|
| API1 | Broken Object Level Authorization | Cambiar /invoices/1 por /invoices/2 y ver la ajena | Comprobar propiedad del objeto en cada acceso |
| API2 | Broken Authentication | alg=none, tokens sin caducidad, login sin límite | OAuth2/OIDC bien validado, límites en el login |
| API3 | Broken Object Property Level Authorization | Excessive data exposure + mass assignment | DTO de entrada y de salida con lista blanca |
| API4 | Unrestricted Resource Consumption | Scraping masivo, fuerza bruta, coste en terceros | Rate limiting + cuotas + límites de tamaño |
| API5 | Broken Function Level Authorization | Endpoint /admin protegido solo por oscuridad | Rol/permiso declarado por endpoint; denegar por defecto |
| API6 | Unrestricted Access to Sensitive Business Flows | Comprar todo el stock con un bot | Detección de automatización, límites de negocio |
| API7 | Server-Side Request Forgery | Webhook que apunta a 169.254.169.254 | Lista blanca de destinos, bloquear rangos internos |
| API8 | Security Misconfiguration | CORS abierto, stack traces, cabeceras ausentes | Hardening por defecto, revisión de configuración |
| API9 | Improper Inventory Management | /api/v1 olvidado, staging con datos reales | Inventario de versiones y entornos; apagar lo viejo |
| API10 | Unsafe Consumption of APIs | Confiar a ciegas en la respuesta de un tercero | Validar y sanear también lo que llega de otras APIs |
Marco legal y de uso responsable
Todo lo descrito aquí es para auditar tu propia API o una para la que tengas autorización escrita del responsable · Probar estas técnicas —enumerar identificadores, saltarse la autorización, forzar endpoints— contra servicios de terceros sin permiso encaja en los delitos de acceso ilícito a sistemas informáticos y de descubrimiento y revelación de secretos (arts. 197 bis, 197 ter y 264 del Código Penal español) · Monta una API de pruebas propia en un entorno aislado o usa laboratorios preparados para ello (crAPI de OWASP, VAmPI) para practicar
Conclusión
La seguridad de una API no se resuelve con un WAF delante ni con «ya usamos HTTPS». Los fallos que más duelen aquí son de lógica: autorización que se comprueba a medias, respuestas que devuelven de más, endpoints sin límites y versiones viejas que nadie apagó. Ninguno lo detecta un escáner genérico, porque todos dependen de qué significan tus datos y quién debería poder tocarlos.
El orden de prioridades es bastante claro: primero autorización objeto a objeto y a nivel de función en cada endpoint, luego una autenticación estándar bien validada, después rate limiting y cuotas como control de seguridad de pleno derecho, y por encima de todo un contrato OpenAPI que se valide en ejecución. Con eso cubierto, ocho de los diez puntos de OWASP dejan de aplicar a tu API.
Referencias
OWASP API Security Top 10 (2023) — owasp.org/API-Security · OWASP crAPI y VAmPI — APIs deliberadamente vulnerables para practicar en local · OpenAPI Specification 3.1 y herramientas de validación en ejecución (express-openapi-validator, Connexion, Zod/JSON Schema) · IETF RFC 9331 y draft de cabeceras RateLimit — el estándar en curso para RateLimit-Limit/Remaining/Reset · Nuestros artículos sobre OWASP Top 10:2025, JWT a fondo, CORS y SameSite, y SSRF, para el contexto completo
unfantasmaenelsistema.com | Seguridad, sistemas y todo lo que no debería funcionar (pero funciona)

