Skip to main content

CVE-2026-54002 - Cross-site scripting (XSS) from incomplete HTML/XML sanitization in Dom::sanitize() (Kirby <= 4.9.3,5.0.0-5.4.3)

NetbyteSEC Security Advisory - Cross-site scripting (XSS) from incomplete HTML/XML sanitization in Dom::sanitize() (Kirby <= 4.9.3,5.0.0-5.4.3)

 
TitleCross-site scripting (XSS) from incomplete HTML/XML sanitization in Dom::sanitize() (Kirby <= 4.9.3,5.0.0-5.4.3)
Advisory ID: NBS-2026-0001
Product: Kirby
Affected Version<= 4.9.3,5.0.0-5.4.3
CVE ID: CVE-2026-54002
Author: Shafiq Aiman | NetbyteSEC
 
 
Vendor/Product Description
 
Kirby CMS is a PHP-based, file-driven content management system that enables users to create and manage websites using flat-file content storage, a customizable administration panel, and an extensible plugin architecture.
 
 
Vulnerability Overview
 

Kirby CMS is vulnerable to stored cross-site scripting (XSS), a vulnerability that was identified in the Dom::sanitize() method used by the writer and list fields (and the broader Sane HTML/SVG/XML sanitization API). When an unknown HTML/XML tag was unwrapped during sanitization, its child nodes were copied into the cleaned output without being passed through the sanitization rules themselves. This indicates an incomplete sanitization pass, which may allow an authenticated user to store malicious script content that executes in the Panel or site frontend when viewed by other users, including higher-privileged admins.

The vulnerability was successfully patched by Kirby on the 4.9.4 and 5.4.4 release versions (June 2026).

 

Proof-Of-Concept
 
An authenticated standard user (a non-admin editor with permission to edit page content) is required to exploit this vulnerability.  

Figure 1: Curl request with XSS payload
 
 
Figure 2: The payload triggered
 

Technical Details
 
First, a little background on how Kirby cleans HTML. When you save content, Kirby goes through a checklist of every tag in your text and decides what to do with each one: keep the safe ones, delete the dangerous ones, and strip out anything risky.
 
There is one special case. If Kirby sees a tag it does not recognize (an "unknown"  tag, like <marquee> or any made-up tag), it does not throw away everything inside it. Instead it removes only that tag but keeps the content inside it. Kirby calls this "unwrapping". This is done by a small function called unwrap(), and this is where the bug lives.

Snippet from src/Toolkit/Dom.php.
public static function unwrap(DOMNode $node): void
{
    foreach ($node->childNodes as $childNode) {
        // skip plain text
        if ($childNode instanceof DOMText) {
            continue;
        }

        // keep the inside content by making a COPY of it  <-- the bug
        $node->parentNode->insertBefore(clone $childNode, $node);
    }

    // delete the unknown tag (and its original content)
    static::remove($node);
}

The key word is clone. To keep the unknown tag's contents, Kirby copies everything inside it, then deletes the original. Kirby's tag checklist was built before this copying, so the fresh copies were never on it and never get cleaned.

 

The affected versions (<= 4.9.3, 5.0.0-5.4.3); Time testing version as shown below:

Figure 3: Kirby affected version


 
Mitigation

 At this time, the vulnerability has been successfully addressed in Kirby 4.9.4 and 5.4.4. Users are advised to upgrade to the patched release for their major version (4.9.4 or 5.4.4 or newer) as soon as possible.

 

Vendor Contact Timeline

2026-06-02: Contact and submit vulnerability disclosure to vendor through email
2026-06-02: Vendor acknowledged the report
2026-06-17: Vendor fixed the vulnerability
2026-06-18: Public release of security advisory
 
 
NetByteSEC Sdn Bhd

===================

NetbyteSEC Sdn Bhd was incorporated under the Malaysian Companies Act 1965 in 2013.
NetbyteSEC is privately owned and is based in Cyberjaya, Selangor, Malaysia.
More information about NetbyteSEC Sdn Bhd can be found at: 
https://www.netbytesec.com