CVE-2025-68436

Face/Off: Hijacking Sensitive Assets via Profile Photos in Craft CMS

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 5, 2026·5 min read

Executive Summary (TL;DR)

By exploiting a Mass Assignment flaw in the user profile update logic, an attacker can manipulate the `photoId` parameter. This tricks Craft CMS into believing a restricted file (like a database backup) is the user's new avatar. The system then 'relocates' this sensitive asset into the public-facing profile directory, effectively granting the attacker full read access to files they should never see.

A critical Insecure Direct Object Reference (IDOR) vulnerability combined with Mass Assignment in Craft CMS allows authenticated users to misappropriate any system asset—including private backups and configuration files—by setting it as their profile photo.

The Hook: Your Face Looks Like a Database Backup

Craft CMS is a darling of the PHP world, known for its flexibility and sleek usage of the Yii2 framework. Like any good CMS, it allows users to have profiles, and those profiles have photos. It’s a simple feature: you upload a JPEG, the server stores it, and it links that image ID to your user account.

But what happens when the code trusts you a little too much? In a secure system, when you say "Image #500 is my photo," the system should ask two questions: "Is Image #500 actually an image?" and "Do you actually own Image #500?"

CVE-2025-68436 is the result of Craft CMS failing to ask those questions. It’s a classic tale of Mass Assignment meeting IDOR. The result is a vulnerability where I don't need to upload a photo to change my face. I can just tell the server that your private tax return PDF is my new face. And the server, being the helpful digital butler it is, doesn't just link it—it grabs that PDF from the secure vault and moves it right onto the front porch so everyone can see it.

The Flaw: Mass Assignment & The Lazy Loader

To understand this bug, you have to look at how the Yii2 framework handles data. Yii2 uses a concept called Mass Assignment via the $model->load($_POST) method. This allows developers to take a giant array of form data and dump it directly into a model's attributes in one shot. It is efficient, cleaner than writing fifty lines of $user->name = $_POST['name'], and dangerous as hell if you aren't careful.

To prevent users from overwriting sensitive fields (like isAdmin or passwordHash), models use a safeAttributes() method (or scenarios) to define an allowlist. If a field isn't safe, Mass Assignment ignores it.

In affected versions of Craft CMS, the photoId attribute was implicitly treated as 'safe' during profile updates. This meant that any POST request to users/save-user could include a photoId parameter. The application didn't check if the user had just uploaded that file. It simply accepted the ID provided in the request body.

But the flaw goes deeper. The logic responsible for handling user photos involves asset relocation. If a user sets a new photo, Craft CMS often attempts to move that asset into a specific 'User Photos' volume. So, the attacker isn't just referencing a file; they are triggering a server-side operation that physically moves a file from a potentially restricted storage volume into a public-facing directory.

The Code: Autopsy of a Safe Attribute

The fix was applied in src/elements/User.php and src/controllers/UsersController.php. The core issue was that photoId was allowed to drift in via the request parameters without explicit filtering.

Here is the critical change in the User model. The developers had to explicitly tell the model not to trust the photoId coming from the wild:

// src/elements/User.php
 
public function safeAttributes(): array
{
    // BEFORE: photoId was implicitly included or not excluded
    // AFTER: Explicitly remove 'photoId' from safe attributes
    return ArrayHelper::withoutValue(parent::safeAttributes(), 'photoId');
}

Additionally, the UsersController was patched to validate ownership. Before the fix, the controller blindly passed the photoId to the service layer. The patch forces a check to ensure the asset is either already the user's photo or a temporary upload owned by the current user:

// src/controllers/UsersController.php
 
// Pseudo-code of the added validation logic
if ($photoId) {
    $asset = Asset::find()->id($photoId)->one();
    if (!$asset || !$this->canViewAsset($asset)) {
        throw new ForbiddenHttpException('Nice try, hackerman.');
    }
}

This double-tap fix—hardening the model against mass assignment and hardening the controller logic—effectively closes the hole.

The Exploit: Stealing Secrets with a POST Request

Let's weaponize this. Imagine we are an authenticated user with low privileges (a standard member account). We want to access a sensitive file we know exists in the system—perhaps a database backup stored in a restricted volume with Asset ID 1337.

We don't need fancy tools; curl or Burp Suite will do.

Step 1: The Setup First, we log in normally to get our session cookie.

Step 2: The Attack We intercept the 'Save Profile' request and modify it. We inject the photoId parameter pointing to our target asset.

POST /index.php?p=admin/actions/users/save-user HTTP/1.1
Host: target-craft-cms.com
Cookie: CRAFT_CSRF_TOKEN=...; CraftSessionId=...
Content-Type: application/x-www-form-urlencoded
 
action=users/save-user
&userId=CURRENT_USER_ID
&photoId=1337  <-- The ID of the sensitive file

Step 3: The Relocation Craft CMS receives this, sees photoId=1337, and thinks, "Oh, the user selected this file as their avatar." The internal logic kicks in: "Profile photos belong in the /images/avatars/ folder." It takes Asset 1337 (our database backup), moves it to the public folder, and updates the database.

Step 4: The Loot We simply refresh our profile page or look at the source code for our avatar URL: https://target-craft-cms.com/images/avatars/user_123/db_backup.sql

We download the file, parse the SQL, and dump the users table. Game over.

The Impact: Why This Matters

This vulnerability is a perfect example of how business logic flaws can outweigh complex memory corruptions in terms of real-world impact. We aren't overflowing buffers here; we are abusing the system's own organizational rules.

Confidentiality: High. Attackers can read any asset known to the system, provided they can guess the ID (which is often sequential integers). This includes contracts, invoices, backups, and config files stored as assets.

Integrity: High. The exploit involves moving the file. This alters the state of the file system. A critical document in a 'Secure' folder is now physically located in a 'Public' folder. Even if the vulnerability is patched later, the file might remain in the wrong location.

Availability: Low to Medium. By moving critical assets out of their expected paths, an attacker could potentially break other parts of the site that rely on those files being in specific volumes.

Fix Analysis (1)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
EPSS Probability
0.04%
Top 100% most exploited
25,000
via Shodan

Affected Systems

Craft CMS 4.x < 4.16.17Craft CMS 5.x < 5.8.21

Affected Versions Detail

Product
Affected Versions
Fixed Version
Craft CMS
Pixel & Tonic
< 4.16.174.16.17
Craft CMS
Pixel & Tonic
< 5.8.215.8.21
AttributeDetail
CWE IDCWE-915 (Mass Assignment)
Secondary CWECWE-639 (Insecure Direct Object Reference)
Attack VectorNetwork (HTTP POST)
CVSS v3.17.5 (High)
Exploit StatusFunctional PoC Available
Privileges RequiredLow (Authenticated User)
CWE-915
Improperly Controlled Modification of Dynamically-Determined Object Attributes

The application allows a client to supply multiple attributes in an input, but fails to verify that the attributes are valid for assignment, allowing attackers to modify sensitive data fields.

Vulnerability Timeline

Initial hardening commits pushed
2025-11-18
Patch released in v4.16.17 and v5.8.21
2025-12-03
CVE-2025-68436 assigned
2026-01-10

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.