Déjà Vu RCE: Patching the Patch for Craft CMS (CVE-2025-32432)

You know that feeling when you fix a leaky pipe, only to find another drip springing up right next to it? Sometimes, software patches feel a bit like that. Today, we're diving into CVE-2025-32432, a critical Remote Code Execution (RCE) vulnerability in Craft CMS that serves as a potent reminder that security fixes aren't always one-and-done. Grab your coffee, sysadmins and security folks, because this one was found lurking in the shadows of a previous patch.

TL;DR / Executive Summary

What's the issue? A critical Remote Code Execution (RCE) vulnerability (CVE-2025-32432) exists in Craft CMS.
Who's affected? Craft CMS versions >= 3.0.0-RC1 up to 3.9.14, >= 4.0.0 up to 4.14.14, and >= 5.0.0 up to 5.6.16.
What's the impact? High. Successful exploitation allows attackers to execute arbitrary code on the server, potentially leading to complete system compromise, data theft, or malware distribution. The attack complexity is low.
What's the fix? Update immediately to Craft CMS version 3.9.15, 4.14.15, or 5.6.17 (or later).
The twist? This vulnerability is an incomplete fix for a previous RCE (GHSA-4w8r-3xrw-v25g), highlighting the challenges of thorough patching. It has reportedly been exploited in the wild.

Introduction: The Patch That Needed Patching

Craft CMS is a popular, flexible content management system favored by many developers for its clean architecture and powerful features. One such feature is its robust asset management system, which includes the ability to create image "transforms" on the fly – resizing, cropping, and otherwise manipulating images for different contexts across a website. It's a handy tool, saving developers time and ensuring optimal image delivery.

But what happens when the tool designed to reshape images can be tricked into reshaping your server's security posture? CVE-2025-32432 emerged precisely because the mechanism handling these transformations had a flaw. Even more intriguingly, this wasn't the first time this area of the code was flagged for RCE; this CVE patches a hole left open by a previous fix (GHSA-4w8r-3xrw-v25g). For anyone managing a Craft CMS instance, this is a critical alert – not just because of the RCE, but because it underscores the need for vigilance even after applying security updates. Let's dig in.

Technical Deep Dive: How Image Handles Became Weapon Handles

Craft CMS allows developers to define named "handles" for specific image transformations (e.g., 'thumbnail', 'bannerResize'). When an asset needs transforming, Craft looks up the handle, applies the defined rules (width, height, format, etc.), and generates the new image variant. This often happens via requests to specific controller actions.

The vulnerability resides within the actionGenerateTransform method in src/controllers/AssetsController.php. This action takes parameters like the asset ID and the transform handle to perform the requested image manipulation.

The Root Cause: The core issue stemmed from insufficient validation of the handle parameter received in the request body. While the previous patch (for GHSA-4w8r-3xrw-v25g) likely addressed one way to exploit transform generation, it seems it didn't fully anticipate all the ways the handle input could be malformed or abused. Specifically, CVE-2025-32432 arose because the code didn't strictly enforce that the handle parameter must be a string.

Think of it like a mailroom expecting a package label (a simple string) but instead receiving a complex set of instructions (like an array or object) disguised as a label. If the mailroom clerk doesn't check what the label actually is and just passes it down the line, those instructions could potentially trigger unintended actions in the machinery further down.

In Craft CMS's case, providing a non-string value (like a PHP array or potentially an object) for the handle parameter could bypass expected logic. While the exact downstream exploitation path isn't detailed in the public advisories, common RCE vectors in such PHP scenarios include:

  1. Template Injection: If the handle (or data derived from it) is used insecurely within template rendering (like Twig, Craft's default engine), it could allow executing arbitrary code via template syntax.
  2. Object Instantiation / Property Manipulation: If the non-string handle leads to unexpected object creation or manipulation of existing object properties, it might trigger PHP Object Injection vulnerabilities if a suitable gadget chain exists.
  3. Argument Injection: If the handle influences arguments passed to external commands (like image processing libraries invoked via shell_exec or similar, though less common now), it could lead to command injection.

Attack Vector: An attacker (potentially needing some level of authentication, depending on site configuration) could send a crafted HTTP request to the asset transform generation endpoint (/index.php?p=admin/actions/assets/generate-transform or similar). This request would include the necessary parameters like assetId but provide a malicious payload disguised as the handle parameter (e.g., a PHP array designed to trigger template injection).

Business Impact: An RCE is often the "game over" vulnerability. Attackers can:

  • Steal sensitive data (user info, database contents, configuration secrets).
  • Deface the website.
  • Install backdoors for persistent access.
  • Use the compromised server to host malware or participate in botnets.
  • Pivot deeper into the internal network.

The fact that this was reportedly exploited "in the wild" (according to SensePost's research linked below) means this isn't just theoretical – real-world sites were compromised.

Proof of Concept (Theoretical Example)

Disclaimer: The following is a simplified, theoretical representation of how such a vulnerability might be triggered. It does not represent the exact exploit used in the wild and should only be used for educational purposes.

An attacker might send a POST request to the transform endpoint. Instead of a normal string handle like "thumbnail", they might send a crafted array.

POST /index.php?p=admin/actions/assets/generate-transform HTTP/1.1
Host: vulnerable-craft-site.com
Content-Type: application/x-www-form-urlencoded
Cookie: [AuthenticationCookieIfNeeded]

assetId=123&handle[some_key]=malicious_payload&anotherParam=value

In this hypothetical example, handle is sent as an array (handle[some_key]=...). If the AssetsController didn't validate that handle is a string, this array might be passed to downstream functions. If, for instance, a part of this array was later used in a Twig template render context without proper sanitization, like {{ some_object[user_controlled_key] }} where user_controlled_key is derived from the handle array, it could potentially lead to template injection and RCE.

Mitigation and Remediation: Patch Now!

The fix is straightforward but critical:

  1. Immediate Fix: Update your Craft CMS installation immediately.

    • If you're on the 3.x branch, update to 3.9.15 or later.
    • If you're on the 4.x branch, update to 4.14.15 or later.
    • If you're on the 5.x branch, update to 5.6.17 or later.
      Use Composer (composer update craftcms/cms) or the Craft CMS control panel updater.
  2. Verification: After updating, verify the installed version in the Craft CMS control panel or via Composer (composer show craftcms/cms). Check server logs for any suspicious requests targeting asset transform endpoints around the time the vulnerability was disclosed or potentially earlier, looking for unusual handle parameters.

  3. Long-Term Solutions:

    • Regular Updates: Keep Craft CMS and all plugins up-to-date. Subscribe to security advisories.
    • Web Application Firewall (WAF): A WAF might help block malformed requests or common attack patterns, providing an extra layer of defense.
    • Security Audits: Regularly audit your web applications, especially critical components handling user input and file operations.
    • Principle of Least Privilege: Ensure your web server processes run with the minimum necessary permissions.

Patch Analysis: The Power of is_string()

Let's look at the simple but effective code change introduced in the patch (commit e1c85441fa47...):

--- a/src/controllers/AssetsController.php
+++ b/src/controllers/AssetsController.php
@@ -1197,6 +1197,9 @@ public function actionGenerateTransform(int $transformId = null): Response
         } else {
             $assetId = $this->request->getRequiredBodyParam('assetId');
             $handle = $this->request->getRequiredBodyParam('handle');
+            // Ensure the handle is actually a string before proceeding
+            if (!is_string($handle)) {
+                throw new BadRequestHttpException('Invalid transform handle.');
+            }
             $assetModel = Craft::$app->getAssets()->getAssetById($assetId);
             if ($assetModel === null) {
                 throw new BadRequestHttpException('Invalid asset ID: ' . $assetId);

The fix adds a crucial check: if (!is_string($handle)). This line explicitly verifies that the $handle variable retrieved from the request body is, in fact, a PHP string. If it's anything else (an array, an object, etc.), the code immediately throws a BadRequestHttpException, stopping the execution flow before the potentially dangerous non-string value can be processed further down the line. This simple type check effectively closes the loophole exploited by CVE-2025-32432 by enforcing the expected data type for the transform handle.

Timeline

  • Discovery: Date not specified, but reported by Orange Cyberdefense likely in early 2025.
  • Vendor Notification: Prior to April 10, 2025.
  • Patch Availability: April 10, 2025 (Craft CMS versions 3.9.15, 4.14.15, 5.6.17 released).
  • Public Disclosure: April 24/25, 2025 (GitHub Advisories published).

Lessons Learned: Trust, But Verify (Your Inputs!)

This vulnerability, especially being a follow-up fix, teaches us a few valuable lessons:

  1. Input Validation is Non-Negotiable: Always rigorously validate all inputs, especially those controlling application logic, file operations, or system interactions. This includes checking type, format, length, and allowed characters/values. Don't assume input conforms to expectations.
  2. Patching Requires Thoroughness: When fixing a vulnerability, consider related attack vectors and edge cases. Regression testing and security code reviews are vital to ensure a patch is complete and doesn't introduce new issues. Sometimes, the first fix isn't the last.
  3. Defense-in-Depth Matters: Relying on a single security check can be fragile. Multiple layers (input validation, type checking, secure API usage, WAFs, least privilege) provide resilience.
  4. Monitor Actively: Security advisories are crucial, but monitoring application logs for errors and suspicious activity can provide early warnings of attempted or successful exploitation.

Key Takeaway: Security is an ongoing process, not a destination. Even patched components can harbor residual risks. Stay vigilant, validate everything, and update promptly.

References and Further Reading

Stay safe out there, and double-check those patches! What's the most surprising place you've seen a vulnerability hide after a fix?

Read more