Saltar al contenido principal

Wiedii — Seguridad de APIs

Aplica a todo servicio que exponga una API (REST/HTTP). Complementa la seguridad de contenedores (docker) y las políticas core (politicas-core).

Referencia: OWASP (Open Web Application Security Project) — organización que publica la lista de los 10 riesgos de seguridad más comunes en APIs; esta nota los recorre uno por uno. Edición: API Security Top 10 (2023).

Reglas obligatorias

Health checks: /health y /ready

Toda API debe exponer:

  • /healthliveness: ¿el proceso vive? Respuesta mínima (200), barata, sin tocar dependencias.
  • /readyreadiness: ¿las dependencias (DB, cache, colas) responden? El orquestador/LB no enruta tráfico hasta que devuelva OK.

Ambos sin autenticación (para que LB, orquestador y el HEALTHCHECK del contenedor los consulten) pero sin filtrar internos: nada de versiones, rutas, configuración ni detalle de dependencias en el cuerpo (eso es Security Misconfiguration, API8). El HEALTHCHECK del contenedor apunta a /health (ver docker).

Ejecución y puertos

La API corre non-root, con capabilities dropeadas, y escucha en un puerto no privilegiado (≥1024; convención 8080). La exposición pública en 80/443 la maneja el borde (ingress/reverse-proxy). Ver docker (sección 7 y sección 14) y politicas-core.

Baseline OWASP API Top 10 (2023)

RiesgoQué exige Wiedii
API1 — BOLA (Broken Object Level Authorization)Validar ownership por objeto en cada acceso, no solo que el usuario esté autenticado. Es el #1 de la lista.
API2 — Broken AuthenticationAuthN robusta; tokens con expiración; proteger endpoints de auth contra fuerza bruta (rate limit).
API3 — BOPLA (property level)Validar esquema de request/response; no aceptar ni devolver propiedades de más (sin mass-assignment, sin over-exposure).
API4 — Unrestricted Resource ConsumptionRate limiting, paginación, límites de tamaño de payload y timeouts.
API5 — Broken Function Level AuthorizationAuthZ por función/rol; deny by default.
API6 — Sensitive Business FlowsProteger flujos de negocio abusables (no solo endpoints).
API7 — SSRFAllowlist/validación de URLs que el servidor consume.
API8 — Security MisconfigurationHeaders de seguridad, CORS restrictivo, errores sin stack traces ni internos.
API9 — Improper Inventory ManagementInventario de APIs y versiones; no dejar endpoints viejos/staging expuestos. Versionar (/v1/).
API10 — Unsafe Consumption of APIsDesconfiar también de las APIs de terceros que consumes (validar sus respuestas).

Detalle por riesgo (con ejemplos)

Los ejemplos son pseudocódigo ilustrativo, adaptable a cada stack (Go, PHP/Laravel, Python/FastAPI, JS/TS). Lo importante es el patrón, no la sintaxis.

API1 — BOLA (Broken Object Level Authorization)

El endpoint autentica al usuario pero no verifica que el objeto solicitado le pertenezca. Es el riesgo #1.

# ❌ Vulnerable — devuelve la factura a cualquier usuario autenticado
GET /api/v1/invoices/1043
invoice = db.invoices.find(id) # no comprueba el dueño
return invoice
# Un atacante itera IDs (1042, 1044, …) y lee facturas ajenas.
# ✅ Correcto — validar ownership en cada acceso
invoice = db.invoices.find(id)
if invoice is None or (invoice.owner_id != current_user.id and not current_user.is_admin):
return 404 # 404 (no 403): no confirmar que el objeto existe
return invoice

Regla Wiedii: validar ownership por objeto en CADA acceso, no solo que haya sesión. Responder 404 en lugar de 403 para no filtrar existencia. Usar IDs no adivinables (UUID) como defensa en profundidad.

API2 — Broken Authentication

Autenticación débil: tokens sin expiración, sin rate limit en login, JWT mal verificado.

# ❌ Vulnerable
jwt.verify(token) # no fija algoritmos → acepta alg="none"
# /login sin rate limit ni lockout; contraseñas con hash débil (md5/sha1)
# ✅ Correcto
jwt.verify(token, key, algorithms=["RS256"], max_age="15m") # algoritmo fijo + expiración
# /login con rate limit + backoff/lockout; hashing con argon2id o bcrypt
# access token corto (≤15 min) + refresh token rotatorio

Regla Wiedii: tokens con expiración corta y refresh rotatorio; nunca aceptar alg=none ni algoritmos no fijados; rate limit + backoff en endpoints de auth; hashing fuerte (argon2id/bcrypt).

API3 — BOPLA (Broken Object Property Level Authorization)

Combina mass assignment (entrada) y over-exposure (salida) a nivel de propiedad.

# ❌ Mass assignment — el body trae campos que no debería poder tocar
user.update(request.body) # { "role": "admin" } → escala privilegios

# ❌ Over-exposure — devuelve el modelo crudo
return user # incluye password_hash, tokens, flags internos
# ✅ Allowlist de entrada + DTO explícito de salida
user.update(pick(request.body, ["name", "email"]))
return { "id": user.id, "name": user.name, "email": user.email }

Regla Wiedii: nunca hacer bind directo del body — allowlist de campos editables. Serializar la salida con un DTO/esquema explícito, nunca el modelo de base de datos crudo.

API4 — Unrestricted Resource Consumption

Falta de límites → DoS o costos descontrolados.

# ❌ Vulnerable
GET /api/v1/users # devuelve TODOS los registros, sin paginar
POST /api/v1/upload # sin límite de tamaño de payload
# ✅ Correcto
GET /api/v1/users?limit=50&cursor=... # paginación obligatoria; limit máx 100
# body máx 1 MB, timeout de request 10 s, rate limit 100 req/min por token

Regla Wiedii: paginación obligatoria con tope; límite de tamaño de payload; timeouts; rate limiting por cliente/token.

API5 — Broken Function Level Authorization

El usuario autenticado puede invocar funciones/rutas para las que no tiene rol.

# ❌ Vulnerable — no se comprueba el rol
DELETE /api/v1/users/55 # cualquier usuario autenticado borra usuarios
# ✅ Correcto — deny by default + chequeo de rol por función
@requires_role("admin")
def delete_user(id): ...

Regla Wiedii: deny by default; autorización por función/rol explícita en el backend. Ocultar el botón en el frontend NO es protección.

API6 — Unrestricted Access to Sensitive Business Flows

Flujos de negocio abusables por automatización (compra de stock limitado, registro masivo, reenvío de OTP, scraping).

# ❌ Vulnerable — endpoint de compra sin protección anti-abuso
POST /api/v1/checkout # un bot acapara todo el stock en segundos
# ✅ Correcto
# rate limit por flujo + por usuario/dispositivo, detección de patrones,
# CAPTCHA/desafío donde aplique, límites de negocio (máx N por cuenta)

Regla Wiedii: identificar los flujos sensibles del negocio y protegerlos a nivel de negocio (no solo técnico): límites por cuenta, detección de automatización, desafíos.

API7 — SSRF (Server-Side Request Forgery)

El servidor hace peticiones a una URL controlada por el cliente.

# ❌ Vulnerable
url = request.body.image_url
fetch(url) # el atacante pide http://169.254.169.254/ (metadata del cloud)
# ✅ Correcto
if not host_in_allowlist(url) or is_private_or_link_local(resolve(url)):
return 400
fetch(url, allow_redirects=False, timeout=5) # sin seguir redirects a internos

Regla Wiedii: allowlist de dominios/esquemas; bloquear rangos privados y link-local (169.254.169.254, 10/8, 192.168/16, etc.); no seguir redirects ciegamente; timeouts.

API8 — Security Misconfiguration

Configuración insegura por defecto: errores que filtran internos, CORS permisivo, sin headers de seguridad.

# ❌ Vulnerable
HTTP 500 { "error": "...", "stack": "at db.query (/app/src/db.ts:42) ..." }
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true # combinación peligrosa
# ✅ Correcto
HTTP 500 { "error": "internal_error", "request_id": "abc-123" } # genérico
Access-Control-Allow-Origin: https://app.wiedii.co # allowlist explícita
# headers: Strict-Transport-Security, X-Content-Type-Options: nosniff,
# Content-Security-Policy, Referrer-Policy; debug OFF en producción

Regla Wiedii: errores genéricos sin stack traces ni internos; CORS restrictivo (nunca * con credenciales); headers de seguridad; debug desactivado en producción. /health y /ready tampoco filtran internos.

API9 — Improper Inventory Management

Endpoints/versiones olvidados o entornos no-prod expuestos.

# ❌ Vulnerable
/v1/users (deprecado pero aún vivo y sin parches)
https://staging-api.wiedii.co/... # entorno de pruebas accesible público
# ✅ Correcto
# versionar (/v1/, /v2/), inventario vivo en OpenAPI, plan de deprecación
# y retiro de versiones viejas; entornos no-prod no expuestos públicamente

Regla Wiedii: versionar las APIs, mantener inventario (idealmente OpenAPI), deprecar y retirar lo viejo, y no exponer entornos no productivos.

API10 — Unsafe Consumption of APIs

Confiar ciegamente en las respuestas de APIs de terceros que consumes.

# ❌ Vulnerable
data = third_party.get("/profile")
db.save(data) # se guarda/propaga sin validar
# ✅ Correcto
resp = third_party.get("/profile", timeout=5) # TLS + timeout
data = validate_schema(resp) # validar como input no confiable
db.save(pick(data, ["name", "avatar_url"])) # solo lo esperado

Regla Wiedii: tratar las respuestas de APIs externas como input no confiable: validar esquema, aplicar timeouts y TLS, no propagar datos sin sanear.

TLS

TLS siempre, terminado en el borde (ingress/reverse-proxy). HSTS.

Contrato OpenAPI (recomendado)

Se recomienda describir cada API con una spec OpenAPI (el estándar para documentar endpoints, parámetros y esquemas). Swagger es solo el tooling que la renderiza (Swagger UI), no el estándar. Beneficios: inventario (API9), base para contract testing (trofeo-testing) y documentación viva. Aún no es obligatorio — recomendado.

Observabilidad

Logs y trazas sin PII ni secretos. Registrar eventos de seguridad (auth fallida, authz denegada) para auditoría.

Referencias