Saltar al contenido
Categoria: OPSEC11 min de lectura

Threat Modeling con STRIDE en Sprints: Ejemplo Completo de un Microservicio

Por Lucas Andrade ·

Como aplicar STRIDE en un microservicio real de pagos dentro de un sprint de dos semanas, con diagrama, amenazas priorizadas y mitigaciones accionables.

Threat Modeling con STRIDE en Sprints: Ejemplo Completo de un Microservicio
En este artículo

El threat modeling muere en un cajon cuando se vuelve una reunion de cuatro horas sin owner y un PDF de 40 paginas que nadie vuelve a abrir. En Basilisk OffSec cableamos STRIDE en sprints de dos semanas usando un microservicio de pagos como conejillo de indias: un dev de backend, un SRE, un investigador ofensivo, 90 minutos en el kickoff y 30 minutos de revision a mitad de sprint. La salida no es un documento, son 12 issues de Jira con mitigaciones verificables. Este post muestra exactamente como lo corrimos en el servicio payments-api, que recibe webhooks del PSP y habla con Postgres, Redis y un KMS, y como cada letra de STRIDE se volvio un parche real en codigo dentro del mismo sprint en vez de un hallazgo que envejece en una wiki.

Por que el threat modeling muere en un cajon#

El modo de fallo es predecible: un taller big-bang produce un documento exhaustivo, el documento nunca se convierte en trabajo, y seis semanas despues la arquitectura ya deriva mas alla de el. La solucion es hacer el threat modeling chico, recurrente y atado a la salida. Limitamos el kickoff a 90 minutos, restringimos el alcance a un servicio y sus fronteras de confianza inmediatas, y exigimos que cada amenaza salga de la sala como issue rastreado con criterio de aceptacion, no como un parrafo. El investigador ofensivo mantiene honesta la sesion preguntando "como lo explotaria de verdad" en vez de "es teoricamente malo". El time-boxing fuerza priorizacion: modelas primero los flujos que cruzan una frontera de confianza, porque ahi operan los atacantes reales, y aceptas que una sesion mas corta cada sprint le gana a una heroica que ocurre una vez y se pudre.

El DFD y las fronteras de confianza#

Antes de modelar, dibujamos el Data Flow Diagram en draw.io con cuatro tipos de elemento: entidades externas (el PSP, el frontend), procesos (payments-api, worker-reconciliation), datastores (Postgres tx_db, Redis idempotency_cache) y los flujos entre ellos. Marcamos las fronteras de confianza explicitamente: internet, luego Cloudflare, luego ingress, luego el mesh interno, luego el KMS. El diagrama no necesita ser bonito, necesita ser correcto, y en 25 minutos todos firmaron. Quien haya construido un lab sabe que un diagrama incorrecto lleva a pruebas incorrectas, como cubrimos en Pentest Web desde Cero: Un Lab Seguro con DVWA, Juice Shop y Burp Suite. La misma regla aqui: si tu flujo de webhook no muestra la validacion HMAC antes del parse del JSON, estas modelando el servicio que vive en tu cabeza, no el que esta en produccion.

Spoofing: probar quien llama de verdad#

El spoofing aparecio primero en el flujo PSP a payments-api. El webhook llegaba con un header X-Signature, pero la verificacion corria despues de json.loads(body), dejando una ventana de parser-differential donde un evento falsificado podia procesarse parcialmente antes de que la verificacion de firma fallara. El fix fue validar el HMAC-SHA256 con una clave rotada por KMS antes de tocar el body, usando comparacion de tiempo constante via hmac.compare_digest para evitar un oraculo de timing. Tambien fijamos el algoritmo de firma aceptado en vez de confiar en un header provisto por el cliente, cerrando un downgrade por confusion de algoritmo. La leccion general: autentica el mensaje antes de parsearlo, y nunca dejes que la identidad se afirme con el mismo input no confiable que estas por confiar. Cada entidad externa del DFD recibio la misma pregunta, y ahi se agruparon los hallazgos de spoofing.

Tampering: integridad de datos en reposo y en transito#

El tampering aparecio en Redis: el cache de idempotencia no tenia TTL fijo ni firma, asi que un atacante con acceso a la red interna podia plantar entradas y disparar replays de cargo o suprimir los legitimos. Agregamos un prefijo de key con namespace mas un HMAC corto sobre la key, y forzamos una ACL con requirepass y TLS en Redis 7 para que el datastore deje de ser una zona de confianza blanda solo por estar dentro del mesh. En el cable, exigimos mutual TLS entre la API y el worker para que un sidecar comprometido no pudiera reescribir en silencio mensajes de reconciliacion. La pregunta de modelado para cada flujo y datastore fue directa: si un atacante se sentara aqui, que podria cambiar y lo notariamos. Donde la respuesta fue "cambiar en silencio", agregamos proteccion de integridad, y donde fue "no lo notariamos", el logging del que depende la siguiente letra.

Repudiation: hacer las acciones probables#

El repudiation lo manejamos con una tabla audit_log append-only usando hashes encadenados, de modo que cada registro se compromete con el anterior y un borrado o edicion silenciosa rompe la cadena. Es el mismo patron tamper-evident que usamos al documentar pivoting a traves de redes segmentadas en Pivoting con Chisel y Ligolo-ng: Redes Segmentadas en un Lab de Pentest, aplicado aqui a movimiento de dinero en vez de acciones de operador. Registramos el principal autenticado, el id de request, el hash de estado antes-y-despues y un timestamp monotono para cada cargo, reembolso y decision de reconciliacion. El punto no es juntar logs por juntarlos, es poder probar, tras un incidente, exactamente quien hizo que y en que orden, sin depender de una tabla mutable que un atacante con acceso a la base pudiera reescribir. Los controles de repudiation son baratos de agregar antes y casi imposibles de reconstruir despues.

Information Disclosure: la categoria mas pesada#

El Information Disclosure fue la categoria mas pesada con ocho hallazgos. Los stack traces se filtraban por 500s de FastAPI en un entorno de staging espejado a prod, secretos aparecian en una ruta /debug detras de un header magico que un pasante de 2024 olvido quitar, y el endpoint /metrics de Prometheus exponia labels con card_bin. Lo arreglamos con middleware que solo serializa {error_id, code} al cliente, matamos la ruta debug, y aplicamos un relabel_config en Prometheus para dropear labels sensibles al momento del scrape. Para hacer el impacto concreto para el equipo demostramos un POC equivalente a un SSRF jalando metadata de la nube, un ejercicio documentado en SSRF Desmitificado: Explotando Metadata de la Nube en un Lab AWS Local. Ver un token real jalado de un endpoint de metadata cambio la sala de "ese log esta bien" a "redactar todo en la frontera".

Denial of Service: mas alla del rate limiting#

El Denial of Service no lo tratamos como solo rate limiting. Mapeamos amplificacion algoritmica: un endpoint /search aceptaba un regex provisto por el cliente y pegaba a Postgres con LIKE %term%, que es a la vez un riesgo de ReDoS y de full-scan. Lo reemplazamos por tsvector mas indice GIN y un cap de 64 caracteres en el term, convirtiendo una query sin limite en una acotada. Agregamos un token bucket por API-key en Envoy a 100 rps con burst de 200, y un circuit breaker en el cliente KMS con pybreaker, porque el KMS gestionado tiene una cuota de 1200 ops por segundo por clave y ya habiamos causado un incidente autoinfligido de 14 minutos en enero. El modelado de DoS pregunta donde un input pequeno produce trabajo desproporcionado, y cada punto asi recibio un limite, un cache o un breaker para que un solo llamador no pueda tumbar el servicio.

Elevation of Privilege: cerrar la lista#

El Elevation of Privilege cerro la lista. El JWT interno de la API usaba HS256 con un unico secreto compartido entre seis servicios, lo que significa que un solo servicio comprometido puede acunar tokens aceptados por todos los demas. Migramos a RS256 con claves por servicio guardadas en KMS, claims especificos de audiencia para que un token acunado para un servicio sea rechazado por otro, y validacion completa de exp, nbf e iss en un unico middleware entregado como la libreria interna basilisk-authz==2.3.0. Centralizar la verificacion en una libreria auditada quito la deriva donde cada servicio validaba un poco distinto, que es en si un camino de elevacion. La pregunta de modelado fue simple: si este componente esta totalmente tomado, que gana el atacante en otro lado, y la respuesta de secreto compartido fue "todo", asi que se volvio el fix de mayor prioridad del lote.

Convertir amenazas en issues rastreados#

Al final del sprint cada item se volvio un issue titulado STRIDE-<letra>-<num>: <amenaza> con un tag mitigation:<status>, porque una amenaza sin ticket es una amenaza que no se va a arreglar. De las 12 amenazas levantadas, 9 salieron como PRs mergeados dentro del mismo sprint, 2 se aceptaron como riesgo residual con fecha de revision documentada a 90 dias, y 1 se volvio un epic para refactorizar el modulo de webhook. El costo total fue cerca de 4 horas de reuniones distribuidas mas el trabajo de codigo que ya estaba en el tablero del sprint. La disciplina que lo sostiene es negarse a cerrar la sesion de modelado hasta que cada amenaza levantada tenga owner, estado y criterio de aceptacion verificable, para que la salida sea un backlog que puedes quemar en vez de un reporte que puedes ignorar.

Tooling y gates de CI que lo mantienen honesto#

Respaldamos el modelado manual con automatizacion para que las regresiones no reabran en silencio una amenaza arreglada. El SAST corre con reglas Semgrep custom que codifican los errores especificos que encontramos, como un check HMAC despues de un parse, y el SCA corre con osv-scanner contra el arbol de dependencias. Ambos son gates bloqueantes para hallazgos High y Critical en el pipeline, de modo que un pull request que reintroduce una debilidad modelada rompe CI en vez de shipear. Mantenemos las reglas Semgrep en el mismo repo que el servicio, versionadas junto al codigo que protegen, y las revisamos cuando un nuevo hallazgo STRIDE sugiere un patron que vale atrapar automaticamente. La automatizacion no reemplaza la sesion humana, la hace acumulativa: cada sprint el modelo manual encuentra los problemas nuevos y las reglas aseguran que el sprint pasado siga arreglado.

FAQ: alcanzan de verdad 90 minutos?#

Para un servicio con frontera clara, si, y la restriccion es una virtud, no una concesion. Una caja apretada obliga al equipo a modelar primero los flujos que cruzan una frontera de confianza, donde de verdad viven los bugs explotables, y a diferir el analisis de bajo valor de funciones helper puramente internas. Si un servicio es tan grande que 90 minutos no cubren sus flujos que cruzan frontera, eso es senal de que el servicio hace demasiado y deberia partirse, no de que la sesion deba durar cuatro horas. La cadencia recurrente es lo que hace viable la caja corta: lo que te pierdes este sprint lo atrapas el siguiente, contra una arquitectura que solo derivo dos semanas en vez de seis meses.

FAQ: y si no tenemos investigador ofensivo?#

Puedes correr STRIDE sin un red-teamer dedicado, pero debes importar deliberadamente la mentalidad adversarial que aporta el investigador, porque una sala de constructores tiende a modelar como deberia funcionar el sistema en vez de como se rompe. Asigna a una persona por sesion para jugar al atacante y exigele explotacion concreta, pidiendo "dame el request exacto que abusa esto" en vez de aceptar "eso podria ser riesgoso". Siembra la sesion con un checklist de las seis letras contra cada flujo y una biblioteca de hallazgos pasados para que las preguntas sean estructuradas y no improvisadas. Es menos efectivo que un especialista ofensivo real, pero una rotacion disciplinada del rol de atacante mas los gates automatizados recupera la mayor parte del valor y cultiva el instinto de seguridad en todo el equipo con el tiempo.

Conclusion: un lenguaje comun, no un checklist de auditoria#

STRIDE no es un checklist de auditoria, es un lenguaje comun entre dev, SRE y ofensiva que convierte la arquitectura en una lista priorizada de arreglos. La receta practica es empezar con un DFD de una pagina, forzar las seis letras contra cada flujo que cruza una frontera de confianza, exigir que cada amenaza aterrice como issue con criterio de aceptacion verificable, y respaldar todo con gates de SAST y SCA para que las amenazas arregladas sigan arregladas. Correlo cada sprint contra un alcance chico en vez de una vez contra todo, manten la sesion honesta con un rol de atacante, y mide el exito por PRs mergeados en vez de paginas escritas. Si no se convierte en codigo este sprint, no fue threat modeling, fue teatro.

Related posts

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly