CVE-2026-54064: NukeViet CMS Multiple Anti-XSS Filter Bypasses Leading to Stored XSS
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.
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 	 but not the decimal equivalent 	. The keyword-blocking regex uses \s* between letters, which does not match HTML entities, so jav	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.
<!-- 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 (	) inside javascript: URI -->
<!-- Submit as a link inside article body -->
<a href="jav	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 	 (hex) but missed 	 (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 	 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('/�*(?: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).
Related research
- high · 8.2CVE-2026-48118CVE-2026-48118: NukeViet Unauthenticated Reflected XSS via Base64-Encoded Comment Status Parameter
- high · 8.7CVE-2026-49259CVE-2026-49259: NukeViet CMS Stored XSS via Comment Reply Handler
- high · 8.7CVE-2026-54065CVE-2026-54065: NukeViet Path Traversal to Arbitrary File Deletion
- high · 7.2CVE-2026-55372CVE-2026-55372: NukeViet Pre-auth SSRF via X-Forwarded-Host