Aller au contenu
Categoria: OPSEC11 min de lecture

Threat Modeling STRIDE en Sprints : Exemple Complet sur un Microservice

Por Lucas Andrade ·

Comment appliquer STRIDE a un vrai microservice de paiement dans un sprint de deux semaines, avec DFD, menaces priorisees et mitigations actionnables.

Threat Modeling STRIDE en Sprints : Exemple Complet sur un Microservice

Le threat modeling meurt dans un tiroir quand il devient une reunion de quatre heures sans owner et un PDF de 40 pages que personne ne rouvre. Chez Basilisk OffSec nous cablons STRIDE dans des sprints de deux semaines avec un microservice de paiement comme cobaye : un dev backend, un SRE, un chercheur offensif, 90 minutes au kickoff et 30 minutes de revue a mi-sprint. La sortie n'est pas un document, ce sont 12 issues Jira avec des mitigations verifiables. Ce billet montre exactement comment nous l'avons execute sur le service payments-api, qui recoit des webhooks du PSP et parle a Postgres, Redis et un KMS, et comment chaque lettre de STRIDE est devenue un vrai patch dans le code au sein du meme sprint plutot qu'un finding qui vieillit dans un wiki.

Pourquoi le threat modeling meurt dans un tiroir

Le mode d'echec est previsible : un atelier big-bang produit un document exhaustif, le document n'est jamais transforme en travail, et six semaines plus tard l'architecture a de toute facon derive au-dela. Le remede est de rendre le threat modeling petit, recurrent et lie a la sortie. Nous plafonnons le kickoff a 90 minutes, restreignons le perimetre a un service et ses frontieres de confiance immediates, et exigeons que chaque menace sorte de la salle comme issue suivie avec critere d'acceptation, pas comme un paragraphe. Le chercheur offensif garde la session honnete en demandant "comment l'exploiterais-je vraiment" plutot que "est-ce theoriquement mauvais". Le time-boxing force la priorisation : on modelise d'abord les flux qui traversent une frontiere de confiance, car c'est la qu'operent les vrais attaquants, et on accepte qu'une session plus courte chaque sprint batte une session heroique qui arrive une fois et pourrit.

Le DFD et les frontieres de confiance

Avant de modeliser, nous avons dessine le Data Flow Diagram dans draw.io avec quatre types d'element : entites externes (le PSP, le frontend), processus (payments-api, worker-reconciliation), datastores (Postgres tx_db, Redis idempotency_cache) et les flux entre eux. Nous avons marque les frontieres de confiance explicitement : internet, puis Cloudflare, puis ingress, puis le mesh interne, puis le KMS. Le diagramme n'a pas besoin d'etre joli, il doit etre correct, et en 25 minutes tout le monde a valide. Qui a construit un lab sait qu'un diagramme faux mene a des tests faux, comme couvert dans Pentest Web depuis Zero : un Lab Sur avec DVWA, Juice Shop et Burp Suite. Meme regle ici : si votre flux de webhook ne montre pas la validation HMAC avant le parse du JSON, vous modelisez le service qui vit dans votre tete, pas celui en production.

Spoofing : prouver qui appelle vraiment

Le spoofing est apparu d'abord sur le flux PSP vers payments-api. Le webhook arrivait avec un header X-Signature, mais la verification tournait apres json.loads(body), laissant une fenetre de parser-differential ou un evenement falsifie pouvait etre partiellement traite avant que la verification de signature n'echoue. Le correctif fut de valider le HMAC-SHA256 avec une cle rotee par KMS avant de toucher le body, avec une comparaison en temps constant via hmac.compare_digest pour eviter un oracle de timing. Nous avons aussi epingle l'algorithme de signature accepte au lieu de faire confiance a un header fourni par le client, fermant un downgrade par confusion d'algorithme. La lecon generale : authentifiez le message avant de le parser, et ne laissez jamais l'identite etre affirmee par le meme input non fiable auquel vous allez faire confiance. Chaque entite externe du DFD a recu la meme question, et c'est la que les findings de spoofing se sont regroupes.

Tampering : integrite des donnees au repos et en transit

Le tampering est apparu dans Redis : le cache d'idempotence n'avait ni TTL fixe ni signature, donc un attaquant avec acces au reseau interne pouvait planter des entrees et declencher des replays de charge ou supprimer les legitimes. Nous avons ajoute un prefixe de cle avec namespace plus un HMAC court sur la cle, et impose une ACL avec requirepass et TLS sur Redis 7 pour que le datastore ne soit plus une zone de confiance molle juste parce qu'il siege dans le mesh. Sur le fil, nous avons exige un mutual TLS entre l'API et le worker pour qu'un sidecar compromis ne puisse pas reecrire en silence des messages de reconciliation. La question de modelisation pour chaque flux et datastore etait franche : si un attaquant s'asseyait ici, que pourrait-il changer et le remarquerions-nous. La ou la reponse etait "changer en silence", nous avons ajoute une protection d'integrite, et la ou c'etait "on ne le remarquerait pas", le logging dont depend la lettre suivante.

Repudiation : rendre les actions prouvables

La repudiation a ete geree avec une table audit_log append-only a hachages chaines, de sorte que chaque enregistrement s'engage sur le precedent et qu'une suppression ou edition silencieuse casse la chaine. C'est le meme motif tamper-evident que nous utilisons pour documenter le pivoting a travers des reseaux segmentes dans Pivoting avec Chisel et Ligolo-ng : Reseaux Segmentes dans un Lab de Pentest, applique ici au mouvement d'argent plutot qu'aux actions d'operateur. Nous journalisons le principal authentifie, l'id de requete, le hash d'etat avant-apres et un timestamp monotone pour chaque charge, remboursement et decision de reconciliation. Le but n'est pas de collecter des logs pour eux-memes, c'est de pouvoir prouver, apres un incident, exactement qui a fait quoi et dans quel ordre, sans dependre d'une table mutable qu'un attaquant avec acces base pourrait reecrire. Les controles de repudiation sont bon marche a ajouter en amont et presque impossibles a reconstruire apres coup.

Information Disclosure : la categorie la plus lourde

L'Information Disclosure fut la categorie la plus lourde avec huit findings. Des stack traces fuyaient via des 500 de FastAPI dans un environnement de staging miroir de la prod, des secrets surgissaient dans une route /debug derriere un header magique qu'un stagiaire de 2024 a oublie de retirer, et l'endpoint /metrics de Prometheus exposait des labels portant card_bin. Nous avons corrige avec un middleware qui ne serialise que {error_id, code} au client, tue la route debug, et applique un relabel_config dans Prometheus pour supprimer les labels sensibles au scrape. Pour rendre l'impact concret pour l'equipe nous avons demontre un POC equivalent a une SSRF tirant les metadata cloud, un exercice documente dans SSRF Demystifie : Exploiter les Metadata Cloud dans un Lab AWS Local. Voir un vrai token tire d'un endpoint de metadata a fait passer la salle de "ce log va bien" a "tout rediger a la frontiere".

Denial of Service : au-dela du rate limiting

Le Denial of Service n'a pas ete traite comme du simple rate limiting. Nous avons cartographie l'amplification algorithmique : un endpoint /search acceptait un regex fourni par le client et frappait Postgres avec LIKE %term%, ce qui est a la fois un risque de ReDoS et de full-scan. Nous l'avons remplace par tsvector plus un index GIN et un plafond de 64 caracteres sur le terme, transformant une requete illimitee en requete bornee. Nous avons ajoute un token bucket par cle API dans Envoy a 100 rps avec burst de 200, et un circuit breaker sur le client KMS avec pybreaker, car le KMS gere a un quota de 1200 ops par seconde par cle et nous avions deja cause un incident auto-inflige de 14 minutes en janvier. La modelisation DoS demande ou un petit input produit un travail disproportionne, et chaque point de ce type a recu une borne, un cache ou un breaker pour qu'un seul appelant ne puisse pas faire tomber le service.

Elevation of Privilege : cloturer la liste

L'Elevation of Privilege a cloture la liste. Le JWT interne de l'API utilisait HS256 avec un unique secret partage entre six services, ce qui signifie qu'un seul service compromis peut forger des tokens acceptes par tous les autres. Nous avons migre vers RS256 avec des cles par service dans le KMS, des claims specifiques d'audience pour qu'un token forge pour un service soit rejete par un autre, et une validation complete de exp, nbf et iss dans un unique middleware livre comme la librairie interne basilisk-authz==2.3.0. Centraliser la verification dans une librairie auditee a supprime la derive ou chaque service validait un peu differemment, ce qui est en soi un chemin d'elevation. La question de modelisation etait simple : si ce composant est entierement possede, que gagne l'attaquant ailleurs, et la reponse du secret partage etait "tout", donc c'est devenu le correctif prioritaire du lot.

Transformer les menaces en issues suivies

A la fin du sprint chaque item est devenu une issue intitulee STRIDE-<lettre>-<num> : <menace> avec un tag mitigation:<statut>, car une menace sans ticket est une menace qui ne sera pas corrigee. Des 12 menaces soulevees, 9 sont sorties comme PRs mergees dans le meme sprint, 2 ont ete acceptees comme risque residuel avec une date de revue documentee a 90 jours, et 1 est devenue un epic pour refactorer le module webhook. Le cout total fut d'environ 4 heures de reunions distribuees plus le travail de code deja au board du sprint. La discipline qui tient est de refuser de cloturer la session de modelisation tant que chaque menace soulevee n'a pas un owner, un statut et un critere d'acceptation verifiable, pour que la sortie soit un backlog a bruler plutot qu'un rapport a ignorer.

Outillage et gates CI qui gardent l'honnetete

Nous avons appuye la modelisation manuelle avec de l'automatisation pour que les regressions ne rouvrent pas en silence une menace corrigee. Le SAST tourne avec des regles Semgrep custom qui encodent les erreurs precises trouvees, comme un check HMAC apres un parse, et le SCA tourne avec osv-scanner contre l'arbre de dependances. Les deux sont des gates bloquants pour les findings High et Critical dans le pipeline, de sorte qu'une pull request qui reintroduit une faiblesse modelisee casse la CI au lieu de livrer. Nous gardons les regles Semgrep dans le meme depot que le service, versionnees a cote du code qu'elles protegent, et les revoyons quand un nouveau finding STRIDE suggere un motif a attraper automatiquement. L'automatisation ne remplace pas la session humaine, elle la rend cumulative : chaque sprint le modele manuel trouve les problemes nouveaux et les regles s'assurent que le sprint precedent reste corrige.

FAQ : 90 minutes suffisent-elles vraiment ?

Pour un service a frontiere claire, oui, et la contrainte est une vertu, pas un compromis. Une boite serree oblige l'equipe a modeliser d'abord les flux qui traversent une frontiere de confiance, ou vivent vraiment les bugs exploitables, et a differer l'analyse a faible valeur des fonctions utilitaires purement internes. Si un service est si gros que 90 minutes ne couvrent pas ses flux traversant la frontiere, c'est un signal que le service en fait trop et devrait etre scinde, pas que la session doive durer quatre heures. La cadence recurrente rend la boite courte viable : ce que vous ratez ce sprint, vous l'attrapez le suivant, contre une architecture qui n'a derive que deux semaines au lieu de six mois.

FAQ : et si nous n'avons pas de chercheur offensif ?

Vous pouvez executer STRIDE sans red-teamer dedie, mais vous devez importer deliberement l'etat d'esprit adversarial que le chercheur fournit, car une salle de builders tend a modeliser comment le systeme devrait fonctionner plutot que comment il casse. Assignez une personne par session pour jouer l'attaquant et tenez-la a une exploitation concrete, en demandant "donne-moi la requete exacte qui abuse ceci" plutot que d'accepter "ca pourrait etre risque". Amorcez la session avec une checklist des six lettres contre chaque flux et une bibliotheque de findings passes pour que les questions soient structurees et non improvisees. C'est moins efficace qu'un vrai specialiste offensif, mais une rotation disciplinee du role d'attaquant plus les gates automatises recupere l'essentiel de la valeur et cultive l'instinct de securite dans toute l'equipe avec le temps.

Conclusion : un langage commun, pas une checklist d'audit

STRIDE n'est pas une checklist d'audit, c'est un langage commun entre dev, SRE et offensif qui transforme l'architecture en une liste priorisee de correctifs. La recette pratique est de partir d'un DFD d'une page, de forcer les six lettres contre chaque flux qui traverse une frontiere de confiance, d'exiger que chaque menace atterrisse comme issue avec un critere d'acceptation verifiable, et d'appuyer le tout avec des gates SAST et SCA pour que les menaces corrigees le restent. Executez-le chaque sprint contre un petit perimetre plutot qu'une fois contre tout, gardez la session honnete avec un role d'attaquant, et mesurez le succes en PRs mergees plutot qu'en pages ecrites. Si ca ne devient pas du code ce sprint, ce n'etait pas du threat modeling, c'etait du theatre.

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