VYPR
Moderate severityNVD Advisory· Published Feb 24, 2026· Updated Feb 24, 2026

Craft CMS has Stored XSS in Table Field via "HTML" Column Type

CVE-2026-27126

Description

Craft is a content management system (CMS). In versions 4.5.0-RC1 through 4.16.18 and 5.0.0-RC1 through 5.8.22, a stored Cross-site Scripting (XSS) vulnerability exists in the editableTable.twig component when using the html column type. The application fails to sanitize the input, allowing an attacker to execute arbitrary JavaScript when another user views a page with the malicious table field. In order to exploit the vulnerability, an attacker must have an administrator account, and allowAdminChanges must be enabled in production, which is against Craft's security recommendations. Versions 4.16.19 and 5.8.23 patch the issue.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Craft CMS table field stored XSS allows arbitrary JavaScript execution via unsanitized html column type, requiring admin privileges and allowAdminChanges enabled.

Vulnerability

Overview

CVE-2026-27126 is a stored Cross-Site Scripting (XSS) vulnerability in Craft CMS's editableTable.twig component when the html column type is used. The application fails to sanitize input for this column type, allowing an attacker to inject arbitrary JavaScript that executes when another user views a page containing the malicious table field [1]. The issue affects Craft CMS versions 4.5.0-RC1 through 4.16.18 and 5.0.0-RC1 through 5.8.22 [3].

Exploitation

Prerequisites and Method

Exploitation requires an attacker to have an administrator account, and allowAdminChanges must be enabled in production—a configuration against Craft's security recommendations [1]. The attack involves creating a table field with a Single-line text column, then intercepting the save request to change the column type to html and injecting a payload (e.g., ``) into the default values [3]. The payload is stored and executed when other users view the compromised page or when an administrator edits the field configuration [3].

Impact

Successful exploitation allows execution of arbitrary JavaScript in the context of any user visiting affected pages. This can lead to data exfiltration, session hijacking, or further compromise of the Craft CMS installation. The vulnerability is particularly significant because administrators might unintentionally create such fields, especially since the html column type is not available in the UI dropdown and must be injected via manipulated requests [2][3].

Mitigation

Craft CMS has released patches in versions 4.16.19 and 5.8.23 that validate column types against a whitelist, preventing the html type from being set outside the intended interface [2][3]. Additionally, disabling allowAdminChanges in production environments prevents exploitation, even on unpatched versions [1][3]. Users are strongly advised to update to the latest patched versions and adhere to security configuration best practices.

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
craftcms/cmsPackagist
>= 4.5.0-RC1, < 4.16.194.16.19
craftcms/cmsPackagist
>= 5.0.0-RC1, < 5.8.235.8.23

Affected products

1

Patches

1
f5d488d9bb6e

Ensure column types are within the supported range

https://github.com/craftcms/cmsbrandonkellyJan 14, 2026via ghsa
1 file changed · +34 27
  • src/fields/Table.php+34 27 modified
    @@ -39,6 +39,8 @@
      */
     class Table extends Field
     {
    +    private static array $typeOptions;
    +
         /**
          * @inheritdoc
          */
    @@ -55,6 +57,31 @@ public static function valueType(): string
             return 'array|null';
         }
     
    +    private static function typeOptions(): array
    +    {
    +        if (!isset(self::$typeOptions)) {
    +            self::$typeOptions = [
    +                'checkbox' => Craft::t('app', 'Checkbox'),
    +                'color' => Craft::t('app', 'Color'),
    +                'date' => Craft::t('app', 'Date'),
    +                'select' => Craft::t('app', 'Dropdown'),
    +                'email' => Craft::t('app', 'Email'),
    +                'heading' => Craft::t('app', 'Row heading'),
    +                'lightswitch' => Craft::t('app', 'Lightswitch'),
    +                'multiline' => Craft::t('app', 'Multi-line text'),
    +                'number' => Craft::t('app', 'Number'),
    +                'singleline' => Craft::t('app', 'Single-line text'),
    +                'time' => Craft::t('app', 'Time'),
    +                'url' => Craft::t('app', 'URL'),
    +            ];
    +
    +            // Make sure they are sorted alphabetically (post-translation)
    +            asort(self::$typeOptions);
    +        }
    +
    +        return self::$typeOptions;
    +    }
    +
         /**
          * @var bool Whether the rows should be static.
          * @since 4.5.0
    @@ -149,14 +176,6 @@ public function __construct($config = [])
                             }
                         }
                     }
    -                // encode the default cell values for html-type cells
    -                if ($col['type'] == 'html') {
    -                    foreach ($config['defaults'] as &$row) {
    -                        if (isset($row[$colId])) {
    -                            $row[$colId] = Html::encode($row[$colId]);
    -                        }
    -                    }
    -                }
                 }
             }
     
    @@ -198,7 +217,13 @@ protected function defineRules(): array
          */
         public function validateColumns(): void
         {
    +        $typeOptions = self::typeOptions();
    +
             foreach ($this->columns as &$col) {
    +            if (!isset($typeOptions[$col['type']])) {
    +                $col['type'] = 'singleline';
    +            }
    +
                 if ($col['handle']) {
                     $error = null;
     
    @@ -252,24 +277,6 @@ public function getContentColumnType(): string
          */
         public function getSettingsHtml(): ?string
         {
    -        $typeOptions = [
    -            'checkbox' => Craft::t('app', 'Checkbox'),
    -            'color' => Craft::t('app', 'Color'),
    -            'date' => Craft::t('app', 'Date'),
    -            'select' => Craft::t('app', 'Dropdown'),
    -            'email' => Craft::t('app', 'Email'),
    -            'heading' => Craft::t('app', 'Row heading'),
    -            'lightswitch' => Craft::t('app', 'Lightswitch'),
    -            'multiline' => Craft::t('app', 'Multi-line text'),
    -            'number' => Craft::t('app', 'Number'),
    -            'singleline' => Craft::t('app', 'Single-line text'),
    -            'time' => Craft::t('app', 'Time'),
    -            'url' => Craft::t('app', 'URL'),
    -        ];
    -
    -        // Make sure they are sorted alphabetically (post-translation)
    -        asort($typeOptions);
    -
             $columnSettings = [
                 'heading' => [
                     'heading' => Craft::t('app', 'Column Heading'),
    @@ -291,7 +298,7 @@ public function getSettingsHtml(): ?string
                     'heading' => Craft::t('app', 'Type'),
                     'class' => 'thin',
                     'type' => 'select',
    -                'options' => $typeOptions,
    +                'options' => self::typeOptions(),
                 ],
             ];
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.