Debug Detective : votre WAF mange votre API
L'API ne renvoyait rien. Pas une erreur lisible, pas de stack trace, pas de timeout. Certaines requêtes passaient, d'autres revenaient refusées à la porte, et celles qui échouaient échouaient avant que quoi que ce soit de mon côté ne les voie.

La scène
L'application allait bien. C'est la première chose que je veux dire, parce qu'il m'a fallu des heures pour l'admettre.
Les utilisateurs ouvraient une liste, appliquaient un filtre, et ne recevaient rien. Pas à chaque fois. Un filtre d'un côté et la page revenait pleine. Un filtre de l'autre et elle revenait vide. Même endpoint, même session, même après-midi.
Intermittent est le mot qu'on emploie quand on ne voit pas encore le motif. C'est presque toujours le mauvais mot. Il y a un motif ; vous ne le tenez simplement pas par le bon bout.
Par derrière
Premier réflexe utile quand la bordure est peut-être en cause : passer à côté. Sudo et moi avons appelé l'API directement, derrière le périmètre, avec le filtre qui venait d'échouer.
Elle a répondu. Immédiatement, correctement, en entier.
C'est un résultat net et il vaut la peine de dire tout haut ce qu'il signifie. L'application est innocente. Les données sont là, la requête fonctionne, le code est juste. Quelque chose entre l'utilisateur et l'application refuse la demande.
J'avais la réponse à ce moment-là. Il m'a fallu encore quelques heures pour m'en apercevoir.
La fausse piste
Parce que ce qui se trouve entre l'utilisateur et l'application n'est pas une seule chose. C'est un load balancer, un WAF, une terminaison TLS, des health checks, et tout ce que vous avez assemblé la dernière fois que vous aviez besoin d'un périmètre. Mon contournement avait prouvé que le défaut était dans cette couche, et j'ai lu cela comme "quelque part dans la bordure" plutôt que comme "la chose que je viens de contourner".
Je suis donc parti sur le load balancer. Santé des backends, affinité de session, timeouts, tout le catalogue. Puis CORS, parce qu'une réponse vide dans un navigateur rend suspicieux vis-à-vis du preflight. Puis le jeton, parce que les requêtes étaient authentifiées et que c'était la case suivante à cocher.
L'authentification était valide. Elle était valide sur les requêtes qui passaient et valide sur celles qui échouaient, ce qui aurait dû me dire quelque chose, et ne me l'a pas dit, parce que j'étais occupé à prouver une chose déjà prouvée.
Quelques heures. À cocher des cases déjà cochées.
La révélation
Les requêtes qui échouaient portaient toutes une syntaxe de filtre. Celles qui passaient, non.
Regardez lesquelles meurent. name eq 'a' or 1 est, sémantiquement, quelqu'un qui tente sa chance, et elle passe sans encombre. name='a'-- est ce que produit un constructeur de requêtes ordinaire, et elle est refusée. La règle ne raisonne pas sur du SQL. Elle reconnaît des caractères, et une apostrophe collée à un opérateur est le motif.
Voilà toute l'intermittence. Un utilisateur pouvait filtrer toute la matinée sans rien heurter, puis taper un nom contenant une apostrophe et se cogner au mur.
Les règles préconfigurées de Cloud Armor dérivent de l'OWASP Core Rule Set, et le Core Rule Set comporte une famille de règles pour l'injection SQL. Mon front-end construit ses filtres avec exactement le genre de ponctuation que ces règles ont été écrites pour attraper.
Personne ne m'attaquait. Ma propre interface envoyait quelque chose qui ressemblait à une attaque, et le garde à la porte faisait précisément le travail pour lequel je l'avais engagé.
Tout ce qui est montré ici est reproduit en local contre OWASP CRS, et non capturé sur le système où cela s'est produit. Le mécanisme et la règle sont les mêmes ; le trafic est le mien.
Régler, pas éteindre
La solution tentante est de désactiver les règles d'injection SQL. Je ne l'ai pas fait, et je plaiderais contre.
Ce que j'ai fait, c'est baisser la sensibilité de ce jeu de règles. La règle reste dans le chemin, elle continue de scorer, et le seuil se déplace là où mon propre trafic passe. C'est un changement plus petit et plus honnête qu'une exception, parce qu'il ne creuse pas un trou à la forme de mon application avant d'oublier que le trou existe.
Ce que j'ai trouvé en passant
Deux choses, et aucune n'était ce que j'étais venu chercher.
La première : rien n'alertait sur les blocages. Le périmètre refusait du trafic sans le dire à personne. La seule raison pour laquelle j'ai su pour celui-ci, c'est qu'un humain l'a remarqué et l'a dit. Tous les autres refus, pendant je ne sais combien de temps, partaient dans un journal que personne ne lisait. Un contrôle incapable de vous dire quand il se déclenche n'est pas observable, et un contrôle non observable est un pari que vous avez accepté de croire.
La seconde est pire, dans l'autre sens. Certains services n'avaient aucune politique attachée. J'étais entré convaincu que mon problème était un excès de blocage, et j'ai trouvé des morceaux du parc derrière le même load balancer sans rien devant eux.
Le périmètre était donc à la fois trop agressif là où il regardait et absent là où il ne regardait pas. Les deux sont la même défaillance sous des habits différents : personne n'avait regardé.
Note du détective
Le périmètre fait partie du système. C'est la seule partie qui peut refuser une requête sans qu'une ligne de votre code ne s'exécute, ce qui est exactement ce qui la rend invisible quand elle le fait.
- Passez à côté de la bordure tôt. C'est une commande, et cela partitionne tout le problème.
- Puis croyez ce que ce contournement vous a dit. Un contournement propre nomme la couche. Lisez le nom.
- "Intermittent" décrit votre compréhension, pas la panne. Cherchez ce que les échecs ont en commun.
- Alertez sur les blocages. Un WAF qui refuse du trafic en silence refusera le mauvais trafic en silence.
- Votre WAF ne connaît pas votre application. Il connaît des motifs, et votre constructeur de requêtes en écrit peut-être.
Votre périmètre vous prévient-il quand il dit non ?
La plupart des équipes découvrent que leur WAF bloque de vrais utilisateurs par un ticket de support. Si vous voulez un second regard sur le vôtre, écrivez-moi.
Contactez-nous
flochai

