CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27126
5.9

Table for Two: Serving Stored XSS in Craft CMS Hidden Features

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 24, 2026·6 min read·14 visits

PoC Available

Executive Summary (TL;DR)

Craft CMS contained a 'ghost' column type called 'html' within its Table Field component. While not visible in the UI, it was supported by the backend. Attackers with field configuration permissions could manually set a column to this type, enabling the rendering of raw, unsanitized HTML (and JavaScript) in the admin panel.

A hidden, semi-internal 'html' column type in Craft CMS's Table Field component allowed authenticated administrators to inject arbitrary JavaScript. By manipulating field configuration requests, an attacker could enable this unexposed column type, bypassing sanitization and achieving Stored XSS against other control panel users.

The Hook: Ghost Features and Zombie Code

We all love a flexible Content Management System. Craft CMS is arguably one of the best, giving developers granular control over data structures. One of its most powerful tools is the 'Table Field,' which allows content editors to manage rows of repeated data—text, numbers, checkboxes, etc. It is the Excel spreadsheet of the CMS world.

But here is the thing about software development: features get deprecated, hidden, or half-built, and sometimes they never really leave the codebase. They just become ghosts. In CVE-2026-27126, researchers found one of these ghosts lurking in the Table Field logic.

It turns out, while the UI only let you pick mundane column types like singleline or number, the backend logic was secretly holding a candle for a type called html. And unlike its siblings, the html type did exactly what it sounds like—it took whatever input you gave it and dumped it directly into the DOM, raw and wriggling. It’s like finding a secret menu item at a restaurant that serves you uncooked chicken.

The Flaw: Trusting the Configuration

The vulnerability stems from a classic disconnect between the User Interface and the Application Logic. The developers correctly assumed that users shouldn't be creating columns that render raw HTML, so they simply didn't include html in the dropdown menu of the Field Settings page. Case closed, right? Wrong.

The backend component responsible for validating the Table Field configuration (src/fields/Table.php) had a blind spot. It accepted the column configuration array and processed it, but it didn't strictly validate that the requested type was one of the publicly supported options.

Simultaneously, the frontend template responsible for rendering the table in the control panel (editableTable.twig) had logic explicitly designed to handle this html type. It was likely a remnant of an internal feature or a deprecated capability. When the renderer encountered a column with type: 'html', it skipped the usual output encoding and treated the cell's content as trusted markup. This created a perfect Stored XSS vector for anyone who could force the configuration to acknowledge this hidden type.

The Code: The Smoking Gun

Let's look at the PHP code. Before the patch, the validateColumns method was a bit too permissible. It iterated through columns to check basic properties but failed to enforce a strict allowlist on the type attribute.

The fix, implemented by Brandon Kelly, introduces a hardcoded list of allowed types and forces anything else to degrade to a harmless singleline text field.

Before (Conceptual Logic):

foreach ($this->columns as &$col) {
    // Checking label, handle, etc.
    // No check if $col['type'] is actually valid!
}

After (Patched Logic):

// src/fields/Table.php
 
private static function typeOptions(): array
{
    return [
        'checkbox' => true,
        'color' => true,
        'date' => true,
        'select' => true,
        // ... (other safe types)
        'url' => true,
    ];
}
 
public function validateColumns(): void
{
    $typeOptions = self::typeOptions();
 
    foreach ($this->columns as &$col) {
        // If the type isn't in our VIP list, you're getting a text box.
        if (!isset($typeOptions[$col['type']])) {
            $col['type'] = 'singleline';
        }
        // ...
    }
}

By adding this check, the 'ghost' html type is effectively exorcised. Even if an attacker tries to inject it, the system forces it into a singleline type, which escapes all output.

The Exploit: Editing the Uneditable

Since the UI won't let us select the html type, we have to get our hands dirty with a proxy. This is an 'Authenticated Admin' exploit, meaning the attacker needs permissions to configure fields (specifically allowAdminChanges must be on).

Here is the attack chain:

  1. Intercept: The attacker configures a Table Field in the settings. They set up a standard column (e.g., a Single Line Text column) and hit 'Save'.
  2. Modify: Using Burp Suite or Caido, they catch the POST request. In the JSON payload defining the columns, they hunt for "type": "singleline".
  3. Inject: They change the value to "type": "html". The backend, lacking the validation we saw earlier, saves this configuration to the database.
  4. Weaponize: The attacker (or an unwitting editor) goes to an Entry that uses this field. The column looks normal, but it's a trap. The attacker types <img src=x onerror=alert(document.cookie)> into the cell.
  5. Trigger: The next time an administrator views this entry in the control panel, the payload fires. The editableTable.twig sees the html type and renders the script tag immediately.

The Impact: Why Admin XSS Matters

You might be thinking, "If I already have Admin access to change settings, why do I need XSS?" That is a fair question, but it misses the nuance of modern access control.

First, Privilege Escalation. In many organizations, there are tiers of administrators. A 'Site Builder' might have permission to configure fields but not to install plugins or execute PHP. By using this XSS, the lower-tier admin can hijack the session of a Super Admin. Once they have that session, they can install a malicious plugin or edit templates to achieve full Remote Code Execution (RCE) on the server.

Second, Persistence. An attacker who briefly compromises an admin account can plant this 'time bomb' in the database. Even if their original access is revoked, the XSS payload remains in the content, waiting for a high-value target to view the table. It is a backdoor that lives in the legitimate data structure of the site.

The Fix: Cleaning Up

The remediation is straightforward: upgrade. The patch exists in Craft CMS 4.16.19 and 5.8.23. These versions introduce the strict type checking we analyzed above.

If you cannot upgrade immediately, there is a configuration-level mitigation. Craft CMS has a setting called allowAdminChanges. In a properly secured production environment, this should be set to false.

// config/general.php
'allowAdminChanges' => false,

Disabling admin changes prevents anyone from modifying field configurations via the control panel, effectively neutralizing the attack vector (unless the attacker has direct database access, in which case you have bigger problems). This is a best practice for Craft CMS deployment pipelines anyway—treat your production schema as immutable.

Official Patches

Craft CMSGitHub Commit fixing the issue
Craft CMSOfficial Security Advisory

Fix Analysis (1)

Technical Appendix

CVSS Score
5.9/ 10
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N

Affected Systems

Craft CMS 4.x (>= 4.5.0-RC1, < 4.16.19)Craft CMS 5.x (>= 5.0.0-RC1, < 5.8.23)

Affected Versions Detail

Product
Affected Versions
Fixed Version
Craft CMS
Pixel & Tonic
>= 4.5.0-RC1, < 4.16.194.16.19
Craft CMS
Pixel & Tonic
>= 5.0.0-RC1, < 5.8.235.8.23
AttributeDetail
CWE IDCWE-79
Attack VectorNetwork (Authenticated)
CVSS v4.05.9 (Medium)
ImpactStored XSS / Session Hijacking
Privileges RequiredLow (Admin with Settings Access)
Exploit StatusPoC Available

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1185Browser Session Hijacking
Collection
CWE-79
Cross-site Scripting

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Known Exploits & Detection

Manual AnalysisManual modification of POST request parameters to set column type to 'html'.

Vulnerability Timeline

Patch committed by vendor
2026-01-14
GHSA Advisory Published
2026-02-24
CVE Published
2026-02-24

References & Sources

  • [1]GHSA-3jh3-prx3-w6wc
  • [2]Patch Commit

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.