PatchClock

PHP security digest · 30 Oct 2026

AdminNeo SQLite Database-List Drop Arbitrary File Deletion

High CVE-2026-96633

Package
adminneo-org/adminneo
Fixed in
5.5.1
Published
Fri 30 Oct 2026, 22:52 Lisbon
Installs on Packagist
291

In plain words

An authenticated AdminNeo user in SQLite mode with a valid CSRF token can use the database-list drop action to delete files that are not SQLite databases. The deletion is limited to files the PHP process has permission to delete.

Affected versions

PackageAffectedFixed in
adminneo-org/adminneo< 5.5.15.5.1

Am I affected?

Check composer.lock for adminneo-org/adminneo below 5.5.1; 5.5.1 is fixed. If affected, check whether AdminNeo is used in SQLite mode and authenticated users can access the database-list drop action. The flaw requires a valid CSRF token and permission for the PHP process to delete the target file.

What to do

Check which version you have, then update to a fixed release for your branch (5.5.1):

composer show adminneo-org/adminneo | grep versions
composer update adminneo-org/adminneo

If your version constraint in composer.json keeps you below a fixed release, raise it first. Whether your configuration is affected, and the exact version ranges, are in the advisory itself.

If you cannot update yet

The advisory suggests validating deletion targets: require a .db, .sdb or .sqlite filename extension, restrict db[] values to databases returned by AdminNeo's listing, and resolve paths to reject traversal outside the expected SQLite database directory. It also suggests considering deletion only of the currently selected database path.

The advisory's description

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.

Weakness: CWE-73 External Control of File Name or Path. Reported by slpoiuewalspoid-dotcom, vrana.

The advisory

From OSV.dev, which gathers the GitHub Advisory Database, FriendsOfPHP and Drupal security advisories; the summary is theirs. The description, affected versions, weakness and credits are from the GitHub Advisory Database (CC BY 4.0). "In plain words", "Am I affected?" and "If you cannot update yet" are based on that advisory, and checked to name no version it does not.