
Snipe-IT PoC exploit for CVE-2025-59712 and CVE-2025-59713
This PoC provides an automated script to exploit CVE-2025-59712 (XSS in User-Agent header) and CVE-2025-59713 (unsafe deserialization).
These two vulnerabilities were discovered by Synacktiv during vulnerability research on the Snipe-IT software. Snipe-IT is an open-source asset management system based on Laravel and designed to help organizations track and manage their physical and digital assets.
A technical writeup can be found in the dedicated security advisory on Synacktiv's website.
This script provides three subcommands, corresponding to exploitation modes: xss, rce and fullchain.
This subcommand exploits CVE-2025-59712, which allows a low-privileged user to inject arbitrary JavaScript in the HTML page rendered when calling the /reports/activity route. This can only be done by an admin user, or a user that has explicit view permissions for reports. The injection is possible due to unproper sanitization of the User-Agent field when a user updates their profile information.
To do so, it will first log in as the low-privileged user, and then inject the JavaScript code defined in the file supplied via the --javascript flag.
$ python3 exploit.py xss --help
Usage: exploit.py xss [OPTIONS]
Execute XSS attack only with custom JavaScript payload
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ * --url -u TEXT Target Snipe-IT URL [required] │
│ * --login -l TEXT Low-privileged username [required] │
│ * --password -p TEXT Low-privileged password [required] │
│ * --new-name -n TEXT New name for low-privileged user [required] │
│ * --javascript -js TEXT Path to custom JavaScript file [required] │
│ --proxy -x TEXT HTTP proxy (http://ip:port) │
│ --verbose -v [quiet|normal|verbose] Logging verbosity [default: normal] │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
[!IMPORTANT]
During the security research, tests were carried out on a version of Snipe-IT that, by default, displayed theUser-Agentfield in which the malicious JavaScript is injected. Since commit 10e5d88, theUser-Agentproperty is not displayed by default when the action log table is rendered. The admin that will trigger the XSS needs therefore to have ticked theUser-Agentbox in the view options for Snipe-IT versions ulterior to this commit.
This subcommand exploits CVE-2025-59713, which allows for remote code execution when credentials for an admin or superadmin user are known. To do so, the following steps are performed:
db-dumps/mysql-snipeit.sql file, which creates a new custom field and edits the last action log with a JSON object containing the Laravel pop chain upon restore.[!TIP] By default, Snipe-IT includes the
.envfile in the backup. If the script detects it, theAPP_KEYvalue is displayed and the.envfile is saved in the./outputdirectory (a custom location for that file can be specified with--output-dir).
[!CAUTION] Restoring a backup disconnects all the users connected to the instance.
/api/v1/reports/activity route is called, which triggers the deserialization and executes the Laravel pop chain.$ python3 exploit.py rce --help
Usage: exploit.py rce [OPTIONS]
Execute RCE attack only (requires existing admin account)
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ * --url -u TEXT Target Snipe-IT URL [required] │
│ * --admin-user -au TEXT Admin username [required] │
│ * --admin-pass -ap TEXT Admin password [required] │
│ --output-dir -od PATH Output dir for .env file [default: /home/ninja/Téléchargements/snipe-it-advisory/exploit/output] │
│ --shell-ip -si TEXT Reverse shell IP address │
│ --shell-port -sp INTEGER Reverse shell port │
│ --command -c TEXT Custom command (overrides reverse shell) │
│ --proxy -x TEXT HTTP proxy (http://ip:port) │
│ --verbose -v [quiet|normal|verbose] Logging verbosity [default: normal] │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
The fullchain subcommand aims at chaining both the XSS and RCE vulnerabilities together. It corresponds to a scenario in which you only have access to a low-privileged user and want to achieve remote code execution. This will only works if the user who visits the /reports/activity page is an admin or superadmin. Indeed, only these roles have the rights to create and restore a backup, which is necessary to poison the database and trigger later the Laravel pop chain.
To do so, the script will:
--command flag, same as for the RCE mode).User-Agent field with a <scr> tag to force the victim's browser to source the malicious JavaScript template./reports/activity route, the XSS is triggered and the malicious JavaScript is loaded and executed by the victim's browser. From then on, the same steps as the ones executed in the RCE module are performed, but in JavaScript.This mode leverages several additional JavaScript libraries, all credits go to their respective authors:
$ python3 exploit.py fullchain --help
Usage: exploit.py fullchain [OPTIONS]
Execute full attack chain: XSS -> Backup manipulation -> RCE
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ * --url -u TEXT Target Snipe-IT URL [required] │
│ * --login -l TEXT Low-privileged user's username [required] │
│ * --password -p TEXT Low-privileged user's password [required] │
│ * --new-name -n TEXT New name for low-privileged user [required] │
│ --ip -i TEXT HTTP server IP address (default: auto-detect) │
│ * --port -P INTEGER HTTP server port [required] │
│ --admin-user -au TEXT Admin username to create [default: admin_rce] │
│ --admin-pass -ap TEXT Admin password to create [default: password123] │
│ --shell-ip -si TEXT Reverse shell IP address │
│ --shell-port -sp INTEGER Reverse shell port │
│ --command -c TEXT Custom command (overrides reverse shell) │
│ --proxy -x TEXT HTTP proxy (http://ip:port) │
│ --verbose -v [quiet|normal|verbose] Logging verbosity [default: normal] │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
To run this script, run the following commands:
$ git clone https://github.com/synacktiv/CVE-2025-59712_CVE-2025-59713
$ cd CVE-2025-59712_CVE-2025-59713
$ python3 -m venv .venv && source .venv/bin/activate
$ python3 -m pip install -r requirements.txt
A Dockerfile is also provided in order to try out the script and reproduce the vulnerability exploitation. It pulls a vulnerable version of Snipe-IT, configures the database with php artisan, and sets up two users:
admin with a default password of AdminPassword123!lowpriv with a default password of LowPrivUser123!Before running the container, the .env and snipe-it.conf files need to be updated with the URL of the Snipe-IT instance:
$ sed -i 's|^APP_URL=.*|APP_URL=http://<IP>:<PORT>|' .env
$ sed -i 's|\(ServerName\s*\).*|\1http://<IP>:<PORT>|' snipe-it.conf
The container can then be run:
$ docker build . -t "snipe-it-cve"
$ docker run -p 80:80 --name "snipe-it-debian" snipe-it-cve:latest