high · 8.7CVE-2026-54064Jul 13, 2026

CVE-2026-54064: NukeViet CMS Multiple Anti-XSS Filter Bypasses Leading to Stored XSS

Rohit Hatagale
AI Security Researcher, SecureLayer7

Two input-filter flaws in NukeViet CMS let any authenticated news author permanently inject JavaScript into articles, putting every site visitor's session and data at risk.

Packagenukeviet/nukeviet
Ecosystemcomposer
Affected< 4.6.00
Fixed in4.6.00

The problem

NukeViet CMS versions before 4.6.00 contain two independent filter-bypass paths in `NukeViet\Core\Request` (`vendor/vinades/nukeviet/Core/Request.php`).

The first flaw is in `filterAttr()`. It blocks event-handler attributes with a `/^on/i` regex, but PHP's `trim()` does not strip the ASCII Form Feed character (`\x0C`). Prefixing an attribute name with `\x0C` makes the regex miss it, while browsers treat the character as whitespace and still fire the handler.

The second flaw is in `unhtmlentities()`. It stripped the hex tab entity `&#x09;` but not the decimal equivalent `&#9;`. The keyword-blocking regex uses `\s*` between letters, which does not match HTML entities, so `jav&#9;ascript:` passes the filter and the browser decodes it into a working `javascript:` URI.

Any account with news-posting permission can exploit either path to store JavaScript that executes for every visitor, including administrators, enabling session theft, credential harvesting, and privilege escalation.

Proof of concept

A working proof-of-concept for CVE-2026-54064 in nukeviet/nukeviet, with the exact payload below.

html
<!-- Bypass 1: Form Feed (\x0C / %0C) prefix on event handler attribute -->
<!-- Submit via POST body field: bodyhtml -->
<img src="x" onerror="alert('XSS')">

<!-- URL-encoded equivalent in a POST body: -->
bodyhtml=<img+src%3D"x"+%0Conerror%3D"alert('XSS')">

<!-- Bypass 2: Decimal HTML entity tab (&#9;) inside javascript: URI -->
<!-- Submit as a link inside article body -->
<a href="jav&#9;ascript:alert('XSS')">Click me</a>

Bypass 1 works because `filterAttr()` matched attribute names against `/^on/i` without first stripping non-printing ASCII control characters. The Form Feed byte (`\x0C`, U+000C) is not in PHP's default `trim()` character set, so `\x0Conerror` passes the regex.

HTML5 parsers treat `\x0C` as a whitespace separator, so the browser still recognizes and fires the event handler.

Bypass 2 works because `unhtmlentities()` used `str_ireplace` to remove only `&#x09;` (hex) but missed `&#9;` (decimal) for the tab character. The `javascript:` keyword-blocking regex relied on `\s*` between characters, which matches literal whitespace but not HTML entities.

After the filter passed the encoded URI, the browser decoded `&#9;` back to a real tab, completing a valid `javascript:` scheme.

The fix (4.6.00) addresses both: `filterAttr()` now strips all bytes in `\x00-\x20` from the attribute name before the `/^on/` check (`preg_replace('/[\x00-\x20]/', '', strtolower($attrSubSet[0]))`), and `unhtmlentities()` now also strips decimal HTML entities for all ASCII control characters 0-31 (`preg_replace('/&#0*(?:3[01]|[12][0-9]|[0-9]);/', '', $value)`).

The fix

Upgrade `nukeviet/nukeviet` to version 4.6.00 or later. There is no workaround for earlier versions. After upgrading, verify that `vendor/vinades/nukeviet/Core/Request.php` contains both the control-character strip in `filterAttr()` and the decimal-entity strip in `unhtmlentities()`.

Reported by g03m0n (WhiteHub #4521).

References: [1][2]

Related research