What this actually is.
Technical background, root cause, and affected surface.
Snipe-IT v8.4.1 ActionlogController::displaySig (app/Http/Controllers/ActionlogController.php:31) constructs the file path as config('app.private_uploads').'/signatures/'.$filename without applying basename() to the URL-supplied $filename. An authenticated user with asset view permission can submit a filename containing directory-traversal sequences to read arbitrary files readable by the web process, including /var/www/html/.env which contains APP_KEY (decrypts encrypted DB fields and session cookies) and DB_PASSWORD. On Apache 2.4 single-encoded %2F is normalized (404); double-encoded %252F passes through and HTTP returns 200 OK. Nginx deployments without merge_slashes off are exploitable with standard %2F. Vendor confirmed the fix is already merged to master via PR #18927 on 2026-04-24.
- 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.
C:H · I:N · A:N
CWE-22
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.
Vulnerability discovered during static + dynamic audit of Snipe-IT v8.4.1 in Docker lab (docker-compose.lab.yml
CVE-2026-55474 assigned.
Timeline recorded · Disclosure coordinated by Securin
Cite, verify, go deeper.
Primary sources — NVD, CISA KEV, and machine-readable IoC feed.