{"id":"CVE-2026-96633","kind":"vulnerability","aliases":["CVE-2026-96633","GHSA-6vfx-w7q3-42gq"],"related":[],"title":"AdminNeo SQLite Database-List Drop Arbitrary File Deletion","description":"## 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.","summary":"AdminNeo's SQLite mode trusts file paths submitted to its database-list drop action and passes them straight to unlink(). An authenticated SQLite-mode user can delete any file the PHP process can write, including non-database files.","severity":"high","cvss":8.1,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"adminneo-org/adminneo","fixed":["5.5.1"],"affected":"< 5.5.1","ecosystem":"composer"}],"ecosystems":["composer"],"tags":["adminneo","cwe-73","package","php","self-hosted","sqlite"],"not_affected_if":["auth-required","default-config"],"how_to_check":"- Check the AdminNeo version with `composer show adminneo-org/adminneo` and see if it is below `5.5.1`.\n- Check whether the app is served with the SQLite entry point enabled (`?sqlite=`).","fix":"- `adminneo-org/adminneo` (composer): move to `5.5.1` or later.","status":"open","published_at":"2026-10-30T22:52:58+00:00","first_seen_at":"2026-10-30T22:56:53+00:00","triaged_at":"2026-10-30T22:58:39+00:00","updated_at":"2026-10-30T22:58:39+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-10-30T22:58:39Z","model":"claude-haiku-5-5","output":{"fix":"- `adminneo-org/adminneo` (composer): move to `5.5.1` or later.","reach":"internet","title":"AdminNeo: SQLite database drop deletes arbitrary server files for logged-in users","impact":"medium","summary":"AdminNeo's SQLite mode trusts file paths submitted to its database-list drop action and passes them straight to unlink(). An authenticated SQLite-mode user can delete any file the PHP process can write, including non-database files.","audience":"businesses","category":"package","nicknames":[],"who_cares":["php","adminneo","sqlite","self-hosted"],"confidence":0.85,"fix_status":"available","needs_auth":"yes","worst_case":"Authenticated user deletes arbitrary files writable by the PHP process","analyst_note":"","how_to_check":"- Check the AdminNeo version with `composer show adminneo-org/adminneo` and see if it is below `5.5.1`.\n- Check whether the app is served with the SQLite entry point enabled (`?sqlite=`).","plain_action":"","needs_analyst":false,"not_affected_if":["auth-required","default-config"],"who_is_affected":"Teams running AdminNeo 5.5.0 or older in SQLite mode, where a logged-in user can reach the database-list drop action. The data path is the submitted db[] value, which is unlinked without extension or listing checks. Installations that do not use SQLite mode or that run only trusted admins are lower risk.","user_interaction":"no","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.000824,"input_tokens":5544,"output_tokens":540,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-1':209C '-2026':11A,19A '-73':27B '-96633':12A,20A '/adminneo':24A '/var/www/html':386C '/var/www/html/poc-delete-target.txt':429C,478C '/var/www/html/poc.sqlite':438C '/vrana/adminer/security/advisories/ghsa-6pg3-chwq-wgqc':135C '0':208C,313C '1':356C '2':405C '3':439C '42gq':17A '6vfx':15A 'accept':188C,580C 'action':48B,99C 'admin':132C 'adminneo':1A,22A,25B,34B,76C,139C,169C,360C,365C,423C,546C 'adminneo-org':21A 'allow':77C 'also':433C 'appli':339C,512C 'applic':361C 'application/x-www-form-urlencoded':476C 'arbitrari':7A,86C,150C,581C 'attach':224C 'audit':368C,382C 'authent':56B,79C,138C,449C 'avoid':299C 'basenam':530C 'bind':345C,539C 'bodi':195C 'bool':241C 'build':362C 'call':197C,494C 'caus':182C 'check':282C,295C,343C 'close':244C 'complet':359C 'connect':242C,262C,316C 'consid':568C 'contain':434C 'content':474C 'content-typ':473C 'cooki':472C 'copi':380C 'creat':107C,277C,300C 'create/rename':520C 'csrf':145C,453C 'current':571C 'cve':10A,18A 'cwe':26B 'databas':4A,45B,73B,96C,110C,114C,157C,180C,185C,190C,199C,211C,216C,223C,229C,239C,240C,256C,354C,402C,415C,437C,443C,460C,465C,487C,493C,501C,523C,543C,548C,566C,573C 'database-list':3A,44B,95C,113C,156C,184C,442C 'db':120C,159C,203C,218C,258C,261C,288C,307C,477C,489C,535C,540C 'delet':9A,62B,85C,166C,509C,522C,558C,576C 'direct':122C,235C 'directori':432C,567C 'docker':374C 'doesn':248C 'dotcom':130C 'driver':102C,275C 'drop':6A,47B,98C,116C,198C,214C,215C,222C,238C,335C,445C,480C,481C,486C,492C 'end':533C 'entri':397C 'equival':341C 'even':172C 'exist':267C 'expect':564C 'extens':105C,292C,306C,314C,325C,329C,342C 'fals':269C,331C 'field':160C 'file':8A,39B,64B,74B,87C,104C,152C,175C,247C,266C,302C,410C,416C,524C,583C 'fix':511C 'foreach':255C 'function':237C,294C 'get':243C,263C,317C 'ghsa':14A 'ghsa-6vfx-w7q3-42gq':13A 'github.com':134C 'github.com/vrana/adminer/security/advisories/ghsa-6pg3-chwq-wgqc':133C 'host':32B,471C 'http':390C,466C 'http/1.1':470C 'imag':375C,379C 'impact':136C 'in-memori':226C 'includ':70B 'instead':550C,578C 'known':351C 'lang':210C,265C,319C 'list':5A,46B,97C,115C,158C,186C,352C,444C,549C 'live':452C 'locat':419C 'log':392C 'match':312C 'memori':228C 'minimum':526C 'mode':37B,59B,82C,221C 'name':191C,284C,297C,298C,315C 'nginx':389C 'non':72B,414C,459C,500C 'non-databas':71B,413C,458C,499C 'offici':372C 'one':322C 'oper':280C,577C 'org':23A 'origin':125C 'outsid':562C 'packag':28B 'page':187C 'pass':50B,118C 'path':40B,117C,153C,168C,234C,336C,348C,403C,461C,502C,528C,554C,556C,574C,584C 'pdo':252C 'php':29B,66B,91C,163C,200C,236C,293C,301C,424C 'place':411C 'pleas':320C 'poc':355C 'poc-delete-target.txt':507C 'poc.sqlite':404C 'point':398C 'post':194C,202C,217C,467C,488C 'preg':311C 'prepar':406C 'process':67B,92C,164C,425C 'protect':276C 'queri':204C 'reach':485C 'redirect':205C 'reject':527C,560C 'relat':151C 'renam':109C,279C 'replac':328C 'report':126C 'request':446C,484C,505C 'requir':286C,569C 'resolv':555C 'return':268C,270C,330C,332C,544C 'root':181C 'sdb':289C,308C,536C 'select':189C,464C,572C 'self':31B 'self-host':30B 'server':305C,391C 'session':450C 'seterror':264C,318C 'shape':376C 'slpoiuewalspoid':129C 'slpoiuewalspoid-dotcom':128C 'sourc':369C,383C 'sqlite':2A,33B,36B,58B,81C,101C,140C,179C,220C,274C,283C,291C,296C,309C,353C,396C,401C,436C,468C,491C,519C,538C,565C 'sqlite-mod':57B,80C 'standalon':373C 'start':357C,364C,388C 'str':327C 'straight':52B 'submit':41B,119C,148C,233C,347C,440C,456C,498C,553C,582C 'substr':206C 'suggest':510C 'summari':75C 'target':409C,515C 'token':146C,454C,482C 'travers':561C 'tree':384C 'true':271C,333C 'trust':38B,552C 'type':475C 'unlink':54B,124C,170C,231C,260C,495C 'unlock':246C 'unsecur':304C 'use':321C,370C,399C,428C,447C,517C 'user':60B,83C,141C 'usernam':469C 'valid':103C,144C,378C,427C,516C 'valu':121C,541C 'w7q3':16A 'whose':529C 'window':254C 'work':250C 'writabl':88C,408C,420C 'write':69B","nicknames":[],"mitigations":[],"who_affected":"Teams running AdminNeo 5.5.0 or older in SQLite mode, where a logged-in user can reach the database-list drop action. The data path is the submitted db[] value, which is unlinked without extension or listing checks. Installations that do not use SQLite mode or that run only trusted admins are lower risk.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"first_public_at":"2026-10-30T22:52:58+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-6vfx-w7q3-42gq","url":"https://github.com/advisories/GHSA-6vfx-w7q3-42gq","severity":"high","published_at":"2026-10-30T22:52:58+00:00","modified_at":"2026-10-30T22:53:01+00:00","first_seen_at":"2026-10-30T22:56:53+00:00","fetched_at":"2026-10-30T22:56:53+00:00","note":null}],"documents":[{"id":26529,"url":"https://api.github.com/repos/adminneo-org/adminneo/security-advisories/GHSA-6vfx-w7q3-42gq","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":26530,"url":"https://github.com/adminneo-org/adminneo/security/advisories/GHSA-6vfx-w7q3-42gq","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":26531,"url":"https://github.com/adminneo-org/adminneo/commit/26fcef2f32922629751cbea6c0bb593fbf1e957d","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":26394,"url":"https://github.com/adminneo-org/adminneo/releases/tag/v5.5.1","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"}],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}