Finding de mission (application web métier, client anonymisé). Découvert et exploité par 34zY / Hamza Terir ; outil de flood concurrent développé from scratch. Testé sur instance de staging.
Contexte
Application web métier exposant un endpoint d’import de fichiers XML (format d’échange sectoriel), accessible avec un simple compte utilisateur authentifié, sans privilège particulier.
Comment je l’ai trouvée
En cartographiant les endpoints via un proxy d’interception, j’ai repéré cet upload XML. Réflexe : tester les classiques du parsing XML. XXE d’abord (lecture de fichiers, SSRF), sans succès. J’ai basculé sur l’amplification d’entités (« Billion Laughs ») : des entités qui se référencent en cascade et explosent en mémoire à la résolution.
Particularité de la cible : le corps n’attendait pas du XML brut mais une représentation encapsulée (tableau d’octets dans du JSON), ce qui passait sous le radar des filtres n’inspectant que Content-Type: application/xml. Le backend décodait puis parsait quand même en XML.
<!DOCTYPE lol [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;">
]>
<root><field>&lol3;</field></root>
Envoyé en rafale (plusieurs threads concurrents), le parseur saturait : latence en hausse, erreurs 500 en série.
Reproduction et garde-fous
Le write-up public ne reproduit pas le flood. La vérification minimale utilise un fichier borné et une seule requête :
curl --fail-with-body --max-time 5 \
-H 'Content-Type: application/json' \
--data-binary @fixtures/bounded-xml-wrapper.json \
'https://AUTHORIZED_HOST/api/import'
Le parseur doit d’abord bloquer les DTD et entités externes, puis appliquer des budgets de taille et de temps :
from defusedxml.ElementTree import fromstring
def parse_untrusted_xml(data: bytes):
if len(data) > 1_048_576:
raise ValueError("XML exceeds request budget")
return fromstring(data)
Le suivi CPU/mémoire/latence permet de distinguer un rejet propre d’un worker qui meurt ou redémarre. Le rate-limit ne remplace pas la configuration sécurisée du parseur.
La faille
Le parseur XML résolvait les entités récursives sans limite de profondeur ni de taille (CWE-776 / CWE-400) : pas de borne d’expansion, pas de garde sur la consommation de ressources, pas de rate-limit. Un petit payload répété suffisait à faire grimper CPU et mémoire.
Impact
- Déni de service déclenchable par n’importe quel compte authentifié, sans privilège.
- Dégradation puis indisponibilité du service, pour un coût d’attaque quasi nul.
- Pas de bypass d’auth ni d’exposition de données : impact purement disponibilité.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H ≈ 6.5 (Medium).
Remédiation
- Désactiver le traitement des DTD et des entités externes (
disallow-doctype-decl) ou activer le secure processing du parseur (limites d’expansion). - Rejeter les payloads contenant
<!DOCTYPE/<!ENTITYavant parsing. - Plafonner la taille des requêtes, appliquer un rate-limit par endpoint.
- Appliquer la même validation aux champs encapsulés (tableau d’octets JSON) parsés en XML en aval : ne pas router les filtres de sécurité sur le seul
Content-Type.
CWE-776 (Recursive Entity References) · CWE-400 (Uncontrolled Resource Consumption).