CVE-2026-96633 json
AdminNeo SQLite Database-List Drop Arbitrary File Deletion
published 2026-10-30 9 d ago modified 2026-10-30 9 d ago also GHSA-6vfx-w7q3-42gq
- cvss
- 8.1
- version 3.1, GitHub
- cisa kev
- Not listed
- not in CISA's catalog
- epss
- —
- no EPSS score yet
- public exploits
- 0
- none known
Summary
## Summary
AdminNeo allows an authenticated SQLite-mode user to delete arbitrary files writable by the PHP process through the database-list drop action. The SQLite driver validates file extensions when creating or renaming databases, but the database-list drop path passes submitted `db[]` values directly to `unlink()`.
Originally reported by @slpoiuewalspoid-dotcom in Adminer: https://github.com/vrana/adminer/security/advisories/GHSA-6pg3-chwq-wgqc
## Impact
An authenticated AdminNeo SQLite user with a valid CSRF token can submit an arbitrary relative file path in the database-list `db[]` field. If the PHP process can delete that path, AdminNeo unlinks it even when the file is not a SQLite database.
## Root Cause
The database-list page accepts selected database names from the POST body and calls `drop_databases()`:
```php if ($_POST["db"]) { queries_redirect(substr(ME, 0, -1), lang('Databases have been dropped.'), drop_databases($_POST["db"])); } ```
In SQLite mode, `drop_databases()` attaches an in-memory database and unlinks each submitted path directly:
```php function drop_databases($databases): bool { Connection::get()->close(); // to unlock file, doesn't work in PDO on Windows foreach ($databases as $db) { if (!@unlink($db)) { Connection::get()->setError(lang('File exists.')); return false; } } return true; } ```
The same SQLite driver protects create and rename operations with `check_sqlite_name()`, which requires a `.db`, `.sdb`, or `.sqlite` extension:
```php function check_sqlite_name($name) { // avoid creating PHP files on unsecured servers $extensions = "db|sdb|sqlite"; if (!preg_match("~^[^\\0]*\\.($extensions)\$~", $name)) { Connection::get()->setError(lang('Please use one of the extensions %s.', str_replace("|", ", ", $extensions))); return false; } return true; } ```
The drop path does not apply an equivalent extension check or bind the submitted path to a known listed SQLite database.
## POC
### 1. Start a complete AdminNeo application
Build and start AdminNeo from the audited source using the official standalone Docker image shape. The validation image copies the audited source tree into `/var/www/html` and starts Nginx HTTP server.
Log in through the SQLite entry point using a SQLite database path `poc.sqlite`.
### 2. Prepare a writable target file
Place a non-database file under a location writable by the AdminNeo PHP process. The validation used:
``` /var/www/html/poc-delete-target.txt ```
The same directory also contained a SQLite database:
``` /var/www/html/poc.sqlite ```
### 3. Submit the database-list drop request
Use the authenticated session and live CSRF token, but submit the non-database path as the selected database:
```http POST /?sqlite=&username= HTTP/1.1 Host: <adminer-host> Cookie: <authenticated-session> Content-Type: application/x-www-form-urlencoded
db[]=/var/www/html/poc-delete-target.txt&all=&drop=Drop&token=<live-csrf-token> ```
The request reaches `drop_databases($_POST["db"]),` and SQLite `drop_databases()` calls `unlink()` on the submitted non-database path.
After the request, the `poc-delete-target.txt` was deleted.
## Suggested Fix
Apply the same target validation used by SQLite create/rename before deleting database files. At minimum: - Reject paths whose basename does not end in `.db`, `.sdb`, or `.sqlite`. - Bind `db[]` values to databases returned by AdminNeo's database listing instead of trusting submitted paths. - Resolve paths before deletion and reject traversal outside the expected SQLite database directory. - Consider requiring the current selected database path for delete operations instead of accepting arbitrary submitted file paths.
Patch / fix
- packagist adminneo-org/adminneo 5.5.1
CVSS scores
| assigned by | version | score | vector |
|---|---|---|---|
| GitHub | 3.1 | 8.1 | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H |
Affected packages
| ecosystem | package | affected | fixed in |
|---|---|---|---|
| packagist | adminneo-org/adminneo | <5.5.1 |
5.5.1 |
Classification
- Weakness
-
- CWE-73 External Control of File Name or Path
References
Sources
- GitHub Advisory GHSA-6vfx-w7q3-42gq
Fencer Vulns keeps one entry per vulnerability: the CVE id wins, then GHSA, and records that alias each other are merged, except that two CVEs always keep separate entries. The same data is available as JSON.