What this actually is.
Technical background, root cause, and affected surface.
Snipe-IT v8.4.1 ActionlogController::displaySig builds a signature path from a URL-supplied filename without basename(). Combined with the storage/private_uploads → /var/lib/snipeit/data/private_uploads symlink layout, an authenticated user can traverse out of the signatures directory and read arbitrary files readable by the web process. Apache 2.4 normalizes single-encoded %2F (returning 404), but the request is exploitable via double-encoded %252F (Apache decodes one layer, PHP receives ..%2F and resolves the traversal). Nginx deployments with default settings are exploitable with %2F directly.
- Vendor
- Grokability, Inc.
- Product
- Snipe-IT
- Severity
- Medium
- CVSS Score
- 6.8
- Status
- Fixed
- Vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
- CWE
- CWE-22
From one request
to root shell.
Reproduced in a sandboxed environment. Requires only LAN or WiFi adjacency.
PHP-level confirmation (Artisan tinker, Docker lab against Snipe-IT v8.4.1): $filename = "../../../../../../var/www/html/.env"; $file = config("app.private_uploads")."/signatures/".$filename; $c = file_get_contents($file, false, stream_context_create(["http" => ["ignore_errors" => true]])); // Output: Read success: YES - 1713 bytes // Content (excerpt): APP_KEY=base64:vKNhos5B3r1UzPsdkzjE+atbthUSEb5W7UuUm+DYn7s= // DB_PASSWORD=LabPassword123!
Apache single-encoded %2F returns 404 (path normalization). Nginx default deployments accept %2F directly.
The bug, and the fix.
// app/Http/Controllers/ActionlogController.php:29-41 default: $this->authorize('view', Asset::class); $file = config('app.private_uploads').'/signatures/'.$filename; // ^^^^^^^^^ // $filename from URL — NO basename() applied
$contents = file_get_contents($file, false, stream_context_create([ 'http' => ['ignore_errors' => true] ])); return response()->make($contents)->header('Content-Type', $filetype);
Root cause: Missing input sanitization. The URL-supplied filename is interpolated directly into the filesystem path without basename(). The storage/private_uploads → /var/lib/snipeit/data/private_uploads symlink combined with deeply-nested ../ sequences makes traversal back to the web root trivial. Apache path normalization provides defense-in-depth against single-encoded %2F only; both double encoding and Nginx defaults bypass it.
When does this fire?
All conditions must be true for the exploit to succeed.
1) Attacker holds a valid Snipe-IT session with asset.view permission. 2) Web server passes a path containing decoded slashes to PHP — Apache requires double-encoded %252F (default config); Nginx with default merge_slashes on accepts %2F directly. 3) Target file (e.g. /var/www/html/.env) is readable by the web-process owner.
What an attacker does to you.
Post-exploitation outcomes mapped to CVSS impact metrics.
Disclosure of any file readable by the web user. Highest-impact reads observed in the lab: .env (APP_KEY enables Laravel session-cookie forgery and decryption of encrypted DB fields including 2FA secrets; DB_PASSWORD enables direct MariaDB access), oauth-private.key (forges API tokens for any user), storage/logs/laravel.log (internal stack traces and query data). CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N = 7.7 High.
Fix it. In this order.
A runbook, not a checklist. Sequence matters — assume compromise before you act.
Add basename() to the URL-derived filename before constructing the file path: $filename = basename($filename); $file = config('app.private_uploads').'/signatures/'.$filename; Apply the same fix to getStoredEula() at ActionlogController.php:57. Vendor PR grokability/snipe-it#18927 applies this fix on master (merged 2026-04-24).
disclose@securin.ioVendors moved in days.
Attackers in hours.
Reconstructed from vendor advisories, CISA bulletins, and Securin research records.
2026-04-24 07:55 UTC: Reported to security@snipeitapp.com by disclose@securin.io. | 2026-04-24 07:57 UTC: Vendor auto-escalation reply received. | 2026-04-24 09:34 UTC: Alison Gianotto (Grokability) confirmed fix already merged to master via PR #18927. | 2026-06-29: CVE-2026-55474 assigned.
Timeline recorded · Disclosure coordinated by Securin
Cite, verify, go deeper.
Primary sources — NVD, CISA KEV, and machine-readable IoC feed.