AdvisoriesCVE-2026-86733

CVE-2026-86733

Backup restore could run operating-system commands

High7.2

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

Time
24m
Cost
$0.3252k input · 29k output · 1.27M cached

Product

Snipe-IT

Affected versions

Before 8.7.0

Class

OS command injection CWE-78

Severity

High 7.2

Summary

Overview

Snipe-IT restore streamed the SQL entry from an uploaded backup archive into the MySQL or MariaDB command-line client. That client was started without the option that disables local commands, so parts of the SQL file could be interpreted as operating-system commands.

The caller has to be a superadministrator. Commands then run as the web application user. That account can read database credentials and the application key, and it can change files and data the application can write. The optional backup sanitizer is off by default.

Snipe-IT 8.7.0 adds the client option that stops local command interpretation.

Sequence

Attack path

  1. 01

    Step 1

    A superadministrator uploads a backup archive and starts a restore.

  2. 02

    Step 2

    Restore hands the SQL entry to the database client.

  3. 03

    Step 3

    The client is running in a mode that can treat parts of that file as operating-system commands.

  4. 04

    Step 4

    Those commands run as the Snipe-IT web application user. This is not an unauthenticated issue.

#poc

Proof of concept

The demonstration is withheld. It shows the class of backup the restore path accepted, not a turnkey exploit.

On Snipe-IT before 8.7.0, a superadministrator restore of a crafted backup could cause the database client to run operating-system commands. The same restore on 8.7.0 does not.

Archive layout, the command syntax inside the SQL, and a replayable backup are withheld. Operators should treat backup restore on every release before 8.7.0 as in scope and upgrade.

#exploit

Exploitation

The primitive is operating-system command execution as the web application user, and only after a superadministrator starts a restore. It is not an unauthenticated remote code-execution chain.

A command that can read the process environment or application files can expose database credentials and the application key. What happens after that depends on the host.

No exploit script, crafted backup, or scanner signature is published. The section exists so a defender gets the impact and the bounds.

#details

Technical details

Root cause: restore streamed the SQL member of an uploaded backup into the database command-line client without the flag that disables local client commands.

Affected configuration: Snipe-IT before 8.7.0. The backup sanitizer, which would have reduced this class of input, is optional and off by default. The caller must already be a superadministrator.

The fix in 8.7.0 starts that client with the flag that refuses local command interpretation.

Remediation

Fixed in Snipe-IT 8.7.0.

  • Upgrade to Snipe-IT 8.7.0 or later.
  • Until you can upgrade, keep backup upload and restore limited to people who already administer the server.

How Submersion found it

  1. 01

    Step 1

    The task was to find a user-controlled file or stream passed to an external program that can run local commands from the contents of that input.

  2. 02

    Step 2

    The model searched process execution, image handling, and the backup restore path.

  3. 03

    Step 3

    Restore streams the SQL from an uploaded backup into the database client. The process arguments are fixed.

  4. 04

    Step 4

    On the client used by the reference deployment, that stream can be read as local commands. The caller is a superadministrator.

  5. 05

    Step 5

    The model wrote up the backup-restore finding.

Basin was pointed at Snipe-IT. It received the application and a running environment. It did not receive a vulnerability description, an exploit, a patch diff, or a prior disclosure.

The CVE credits the Submersion AI Security Research Team.

Run log

Interactive trajectory

The recorded Basin run for this finding. Step through the model’s reasoning, the tools it called, and the raw output it read on the way to the vulnerability.

ModelBasin
TargetSnipe-IT
Reasoning steps37
Tool calls54
Run cost$0.32
User

Search this codebase for OS command injection. For every place a user-controlled file or stream is passed to an external program, determine whether that program can execute local commands from the contents of its input, even when the process arguments themselves are fixed. Report the finding end to end including the file, the required user, and the impact.

37 steps · 54 tool calls
Basin

Let me try to obtain a MySQL client to empirically test batch-mode system handling, and meanwhile inspect the web route chain.

Basin

MariaDB is installed. Now let me start it and empirically test whether the MySQL client executes OS commands from a piped SQL dump.

Basin

The MariaDB client executed system id from the piped dump. Let me confirm this rigorously and check which client variants do this.

Basin

MariaDB confirmed: system, \!, and source all execute from a piped SQL dump. Now let me check whether the actual MySQL client behaves the same.

Basin

Critical distinction confirmed: MariaDB client executes system from a piped dump; MySQL client 8.4 does not. Let me verify the deployment defaults that determine which binary is used, and the authorization on these routes.

Basin

The backups routes are nested inside the admin group (which adds authorize:superuser), so a superuser is required. Let me confirm the filename validation and do a final clean end-to-end verification with the MariaDB client.

Basin

Everything is confirmed. Let me clean up the test environment.

Basin

I have a complete, empirically verified picture. Here is the finding.

OS command injection via backup restore (mysql/mariadb client)

The one place in this codebase where a user-controlled stream is handed to an external program is the database restore flow. The external program is the mysql binary at $mysql_binary, and whether it runs local commands from the input depends on whether that binary is a MariaDB client or a MySQL client.

End-to-end chain

  1. UploadPOST /backups/uploadSettingsController::postUploadBackup() (app/Http/Controllers/SettingsController.php, ~line 965) stores the attacker's zip under storage/app/backups/.
  2. RestorePOST /backups/restore/{filename}SettingsController::postRestore() (~line 999) calls Artisan::call('snipeit:restore', ['filename' => storage_path('app/backups/'.$filename), ...]).
  3. Pipingapp/Console/Commands/RestoreFromBackup.php extracts the single SQL stream from the zip ($sql_contents = $za->getStream(...), line ~425) and opens the external program with fixed, escapeshellarg'd arguments (line ~459):
    $proc_results = proc_open("$mysql_binary -h <host> -u <user> -P <port> <db>",
        [0 => ['pipe','r'], 1 => ['pipe','w'], 2 => ['pipe','w']], $pipes, null, $env_vars);
    In the default web path (--sanitize-with-prefix is null), the raw dump is written verbatim into the client's stdin (lines ~496–507). The arguments are fixed and safe — the payload is entirely in the piped content, exactly the case the task targets.

Required user and preconditions

  • User: a superuser (the backups routes are nested inside the admin group in routes/web.php line 156, which adds the authorize:superuser permission check; the inner group only adds auth).
  • app.lock_passwords must be false (not demo/locked mode).
  • DB_DUMP_PATH (default /usr/local/bin, .env.example uses /usr/bin) must contain a mysql binary.

Does the program execute commands from its input?

I installed and tested both client families against a live server:

ClientPiped dump containing system idResult
MariaDB client 13.0.2 (mariadb)executesuid=501(user)... printed; system touch /tmp/final-prove created the file
MySQL client 8.4.0 (mysql)syntax errorno execution

So the binary matters. In the reference Docker deployment (Dockerfile installs Debian mysql-client, which is the MariaDB client; docker-compose.yml uses mariadb:11.4.7), DB_DUMP_PATH=mysql points at a MariaDB client, so the flow is exploitable. MariaDB's client interprets system <cmd>, \! <cmd>, and source <file> from stdin even in batch mode; MySQL's client does not.

Impact

Arbitrary OS command execution as the web server (PHP-FPM/Apache) process user, reachable through the ordinary backup-restore feature. A superuser uploading a crafted zip whose SQL dump contains system <cmd> (or \! <cmd>) then hits the restore endpoint and the command runs as the PHP user. (The flow also runs db:wipe + migrate, so the same user can destroy/replace the database — but the OS command execution is the escalation beyond the app's admin model.)

Other external-program sites examined (no finding)

  • app/Console/Commands/Version.phpshell_exec('git ...'), passthru('npm run prod'): CLI-only, arguments are fixed prompts, no user-controlled file/stream.
  • upgrade.phpgit/composer/artisan shell calls in a CLI upgrade script; not web-reachable.
  • app/Console/Commands/LdapTroubleshooter.php — builds ldapsearch with escapeshellarg'd values; CLI diagnostic.
  • app/Http/Controllers/StorageProxyController.phpfpassthru($stream) streams an S3 object to the response; pure PHP, no external program.
  • spatie/laravel-backup backup:run (vendor, not installed here) — invokes mysqldump with DB connection config (not user-controlled content) and zips with PHP ZipArchive.

Suggested fix (for reference)

Reject/strip meta-commands from the dump before piping: filter out lines starting with system, \!, and source (and their uppercase variants) when writing to $pipes[0], or pipe through the existing SQLStreamer sanitizer even in the default (non-clean) path. Alternatively, use a MySQL client that does not interpret meta-commands in batch mode, or invoke mysql with --binary-mode/a dedicated import path that never parses client meta-commands.

Model and evaluation

Model
Basin
Task framing
The CVE describes a superadministrator backup restore that reaches operating-system commands.
Verifier
Restore of the crafted backup class runs commands before 8.7.0 and does not on 8.7.0.

No exploit, archive, or command syntax is published on this page.

Disclosure

Disclosure timeline

  1. Stage 1

    Fixed in Snipe-IT 8.7.0

  2. Published

    Published as CVE-2026-86733