
# Laboratorio Docker autorizzato e PoC pulito per la validazione della RCE CVE-2026-82222 in GiveWP 4.16.5.1 e della correzione 4.16.7.2.
Materiale di ricerca sulla sicurezza per riprodurre e validare CVE-2026-82222 in un laboratorio Docker isolato.
Per il PoC RCE diretto e la scansione URL usa CVE-2026-8222-RCE.py
Stato: dimostrato nel laboratorio fornito
GiveWP 4.16.5.1 consente a un attaccante inizialmente non autenticato di persistere un grafo di oggetti PHP, riattivarlo tramite la gestione delle sessioni di GiveWP ed eseguire un comando marker fisso come utente web-server di WordPress.
Risultato positivo testato:
GiveWP: 4.16.5.1
WordPress: 6.6.2
PHP: 8.1.30
Result: /tmp/CVE-2026-82222-RCE-GETBAG creato da www-data
Il risultato end-to-end è stato inoltre riprodotto contro il sorgente GiveWP 4.16.5.1 pulito e non strumentato. GiveWP 4.16.7.2 ha bloccato il vettore HTTP nella configurazione testata e ha bloccato indipendentemente il gadget terminale durante un controllo diretto.
Questo dimostra l'esecuzione di comandi all'interno del container WordPress. Non dimostra accesso root, fuga dal container, movimento laterale o compromissione dell'host.
Usa questo repository solo su sistemi di tua proprietà o su cui sei esplicitamente autorizzato a testare.
Il PoC fornito è intenzionalmente limitato:
touch /tmp/CVE-2026-82222-RCE-GETBAG.--allow-authorized-non-loopback.127.0.0.1.Il test HTTP crea un utente donatore usa-e-getta, metadati e righe di sessione GiveWP. Usa il comando di reset incluso dopo il test.
Requisiti:
curlunzipsha256sum o shasumdownloads.wordpress.org per gli archivi ufficiali del pluginEsegui la matrice completa vulnerabile/patchata:
./lab verify
Il comando:
ProviderForwarder patchato rifiuta il callable stringa.Il laboratorio patchato rimane in esecuzione alla fine. Rimuovilo con:
./lab reset
Per usare un'altra porta loopback:
LAB_PORT=8099 ./lab verify
Il controllo vulnerabile deve terminare con evidenza terminale concreta:
[PASS] E1: registrazione non autenticata ha emesso cookie di autenticazione
[PASS] E3: grafo serializzato persistito nel proprio last_name
[PASS] E4: nonce del modulo di donazione ottenuto
[PASS] E5: scrittura sessione raggiunta post-sink HTTP status=500 atteso
[PASS] E6: trigger di lettura/distruzione sessione completato
marker presente e di proprietà dell'utente web WordPress
RISULTATO: CONTROLLO VULNERABILE CONFERMATO
Il controllo patchato deve mostrare:
[PASS] P1: gate di registrazione patchato ha bloccato il cookie di autenticazione
DIRECT_MARKER=absent
marker HTTP assente e gadget terminale diretto bloccato
RISULTATO: CONTROLLO PATCHATO CONFERMATO
Un HTTP 500, payload memorizzato, eccezione o hit del rilevatore senza il marker non viene accettato come prova di RCE.
Avvia e testa la release vulnerabile:
./lab start vulnerable
./lab test
Avvia e testa la release patchata:
./lab start patched
./lab test
Ispeziona lo stato corrente:
./lab status
Rimuovi container, volumi, utenti di test, sessioni e stato del marker:
./lab reset
Gli ZIP del plugin in cache e gli asset estratti vengono conservati per riesecuzioni più veloci. Rimuovi anche quegli asset generati esatti con:
./lab reset --purge-assets
| Versione | Valutazione |
|---|---|
| GiveWP 4.16.5.1 | RCE riprodotta end-to-end |
| GiveWP 4.16.6–4.16.7.1 | Segnalate come interessate; non riprodotte singolarmente qui |
| GiveWP 4.16.7.2 | Controlli negativi patchati riprodotti |
| Release successive | Non testate singolarmente; aggiorna all'ultima release supportata |
L'advisory pubblico identifica le release fino alla 4.16.7.1 come interessate. Questo repository dimostra direttamente solo le due versioni nella sua matrice di test positivo/negativo.
Riferimenti:
Lo sfruttamento combina diversi comportamenti:
Give\Helpers\Utils::maybeSafeUnserialize() usa
allowed_classes => false, producendo __PHP_Incomplete_Class, ma una successiva
serializzazione preserva i nomi e le proprietà delle classi originali.maybe_unserialize() senza restrizioni riattiva le classi incluse.system().Sono richieste quattro barre rovesciate letterali del namespace nel vettore HTTP. I due
passaggi di stripping effettivi le riducono 4 -> 2 -> 1. Il payload è privo di NUL,
e PHP 8.1.30 è stato verificato per idratare il nome serializzato semplice per la
proprietà privata 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')
Grafo controllato dall'attaccante:
TCPDF
├── file_id = identificatore univoco della richiesta
└── imagekeys = Give\Vendors\Symfony\Component\HttpFoundation\Session\Session
├── attributeName = comando marker fisso
└── storage = Give\TestData\Factories\DonationFactory
└── loadedProviders["getBag"] = "system"
La transizione nascosta critica è il dispatch implicito di IteratorAggregate
di PHP. TCPDF::$imagekeys è non tipizzato, quindi assegnare una Session Symfony
fa sì che foreach invochi Session::getIterator().
Il comando marker viene eseguito prima che Symfony applichi il tipo di ritorno
getAttributeBag(): AttributeBagInterface. Il conseguente TypeError e HTTP 500
sono effetti post-sink.
Il grafo candidato rifiutato usava:
TCPDF::$objcopy
-> DonationFactory::$loadedProviders['__destruct'] = 'system'
Quel grafo non può funzionare. TCPDF::_destroy() esegue solo unset di objcopy; PHP non
instrada la distruzione automatica attraverso __call('__destruct', ...).
L'operazione mancante era:
foreach ($this->imagekeys as $file) {
La revisione precedente seguiva i distruttori e le chiamate di metodo esplicite ma non
ispezionava ricorsivamente il protocollo implicito degli oggetti attivato da foreach.
La Session Symfony fornisce il ponte mancante:
foreach invoca getIterator().getIterator() raggiunge getBag($attributeName).storage controllato dall'attaccante è una DonationFactory.getBag non definito invoca ProviderForwarder::__call().loadedProviders['getBag'] = 'system' seleziona il callable.attributeName fornisce l'argomento del comando.Posizioni rilevanti in GiveWP 4.16.5.1:
| Componente | Posizione |
|---|---|
| Unserialize iniziale protetto | src/Helpers/Utils.php:203-217,237-241 |
| Vettore di donazione | includes/process-donation.php:157 |
| Persistenza sessione GiveWP | includes/class-give-session.php:364-368,489-508 |
| Riattivazione sessione senza restrizioni | includes/class-give-session.php:347-350 |
| Distruttore TCPDF | vendor/tecnickcom/tcpdf/tcpdf.php:2050-2052 |
| Iterazione controllata dall'attaccante | vendor/tecnickcom/tcpdf/tcpdf.php:7885-7907 |
| Ponte iteratore Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:134-136 |
Ponte getBag Symfony | vendor/vendor-prefixed/symfony/http-foundation/Session/Session.php:259-283 |
| Callable terminale | src/TestData/Framework/ProviderForwarder.php:19-23 |
| Autoloader Composer GiveWP | give.php:624-625 |
Hash delle release ufficiali:
give.4.16.5.1.zip
95fc6709b6ed284074bf09be096e28d6299fd4a103848c2b1c3bf8a5772cf98c
give.4.16.7.2.zip
c5bc98da2abb748c64d31a43679ff5f223092dfff6d214132a55dcbc948e91ae
Il laboratorio riestrae ogni plugin dal suo ZIP verificato prima di ogni avvio fresco, lo copia in WordPress senza modifiche e confronta byte per byte l'albero completo dei file del plugin distribuito con il sorgente verificato. Le tre immagini Docker base ufficiali sono anche bloccate ai loro digest manifest multi-architettura.
GiveWP 4.16.7.2 aggiunge difese indipendenti su registrazione, gestione dell'input serializzato, riattivazione della sessione, migrazione dei dati e hardening dei gadget.
L'hardening terminale richiede che l'oggetto risolto implementi il contratto provider atteso:
if ( ! $provider instanceof Contract\Provider ) {
return null;
}
La stringa system controllata dall'attaccante viene quindi rifiutata. Il controllo
diretto in questo repository bypassa il vettore HTTP e verifica che questa difesa
terminale da sola lasci il suo marker assente.
Un risultato PoC negativo da solo non dimostra che un sito è patchato. Un WAF, routing diverso, registrazione disabilitata, modulo legacy mancante, configurazione della sessione o funzioni di comando PHP disabilitate possono tutti impedire il marker su un codebase ancora vulnerabile.
Procedura di validazione consigliata:
Controllo versione:
wp plugin get give --fields=name,status,version
Aggiorna GiveWP all'ultima versione supportata. Dopo l'aggiornamento:
TCPDF, Session
Symfony o loadedProviders.Se un'installazione vulnerabile esposta a internet contiene artefatti di iniezione di oggetti, trattala come una potenziale compromissione piuttosto che limitarti ad aggiornare il 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 committato:
ZIP ufficiali del plugin
sorgente GiveWP estratto
sorgente target strumentato
cookie o dati di sessione
evidenze/log di runtime
indirizzi target interni o credenziali riutilizzabili
payload objcopy obsoleti
output di validazione storici
| Affermazione | Stato |
|---|---|
| Vettore di iniezione di oggetti PHP persistente | Dimostrato |
| Riattivazione delle classi incluse | Dimostrato |
| Catena POP stock completa | Dimostrato |
| Marker fisso eseguito come utente web | Dimostrato |
| Riproduzione contro 4.16.5.1 pulito | Dimostrato |
| Controlli negativi contro 4.16.7.2 pulito | Dimostrato |
| Ogni release intermedia interessata testata | Non testata |
| Privilegio root | Non rivendicato |
| Fuga dal container | Non testata |
| Compromissione dell'host | Non testata |
Lo standard di prova è deliberatamente rigoroso: solo un marker di comando osservabile attraverso la catena stock non modificata viene etichettato come RCE. Accettazione HTTP, serializzazione, eccezioni e hit del rilevatore sono evidenze intermedie.