CVE-2026-96633
AdminNeo SQLite Database-List Drop Arbitrary File Deletion
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():
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:
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:
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:
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.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- High
What each source reports
GitHub Advisories Score usedGHSA-6vfx-w7q3-42gq
8.1
AdminNeo SQLite Database-List Drop Arbitrary File Deletion
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():
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:
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:
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:
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.
- Record
GHSA-6vfx-w7q3-42gq- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H - Weakness
- CWE-73
- Updated
- 2026-11-02
OSVGHSA-6vfx-w7q3-42gq
8.1
AdminNeo SQLite Database-List Drop Arbitrary File Deletion
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():
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:
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:
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:
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.
- Record
GHSA-6vfx-w7q3-42gq- CVSS 3.1
- 8.1 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H - Weakness
- CWE-73
- Updated
- 2026-11-02
Affected packages
| Package | Affected | Fixed |
|---|---|---|
adminneo-org/adminneoPackagist |
before 5.5.1 | 5.5.1 |
adminneo-org/adminneoPackagist |
before 5.5.1, plus listed versions v4.10.0v4.11.0v4.12.0v4.13.0v4.14.0 Show 45 moreShow lessv4.15.0v4.16.0v4.16.1v4.17.0v4.17.1v4.17.2v4.2.0v4.2.1v4.2.2v4.2.3v4.2.4v4.2.5v4.3.0v4.3.1v4.4.0v4.5.0v4.6.0v4.6.1v4.6.2v4.6.3v4.7.0v4.7.1v4.7.2v4.7.3v4.7.4v4.7.5v4.7.6v4.7.7v4.7.8v4.7.9v4.8.0v4.8.1v4.8.2v4.9.0v4.9.1v4.9.2v4.9.3v4.9.4v5.0.0v5.0.0-rcv5.1.0v5.1.1v5.2.0v5.2.1v5.3.0 |
5.5.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | GitHub Advisories, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-96633 is a high vulnerability (CVSS 8.1) in adminneo-org/adminneo, published October 30, 2026. No source lists public exploit code. Fixed in adminneo-org/adminneo 5.5.1.
References
Left out here: 3 versions. The JSON record has them.