
Drupal 11.4.8 - High - Remote - XSS - AttributeXss still allows HTMX attrs
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-htmx-attr
Class: Stored XSS Reach: Remote
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-011 closed HTMX attrs in Xss::attributes(). Link and menu attributes go through AttributeXss::sanitizeAttributes, and that skip list still only drops style and on*.
A content editor who can set a Link field (or a menu link) can store hx-on-click or data-hx-on-click. Standard plus BigPipe loads core/htmx. When an admin or any other viewer opens that node and clicks the link, HTMX runs the stored JS in the site origin.
| ID | no CVE yet |
| Class | Stored XSS (HTMX event attrs) |
| Reach | Remote (authenticated Link field / menu writer; victim views and clicks) |
| CWE | CWE-79 |
| CVSS | High: 8.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N |
| Product | Drupal core |
| Affected | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Auth | authenticated user who can write a Link field (or menu link). Victim views the page and clicks. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Writer: a content editor who can set link options.attributes (Link field, menu link, JSON:API). Victim: anyone whose page loads HTMX (Standard + BigPipe loads core/htmx), including a site admin who clicks the link.
| Privilege | Method / path | Kind | Lab-proven or inferred |
|---|---|---|---|
| Persist HTMX event attrs on a link | POST /jsonapi/node/article with field_link.options.attributes.hx-on-click and data-hx-on-click | Integrity of stored field | Lab-proven. JSON:API 201, stored attrs still contain hx-on-click |
Print those attrs on the node <a> | LinkFormatter -> AttributeXss::sanitizeAttributes | Integrity of HTML | Lab-proven. GET /node/1 HTML contains hx-on-click and the marker window['DRUPAL-HTMX-ATTR-WITNESS']=1 |
| Run that JS in the victim origin when they click | HTMX binds hx-on-* | Integrity of victim session | Inferred from HTMX + the page loading core/htmx. Lab did not drive a browser click |
| Act as the viewer (session cookie, admin UI clicks) | Same stored XSS | Integrity of victim session | Inferred. Lab stops at the attribute remaining in HTML |
Negative: Xss::filter | Xss::filter('<a ... hx-on-click=...>') | Closed | Lab-proven closed. xss_keeps_hx=False |
| Unauthenticated write of the link | JSON:API without a session | Closed | Lab logged in as uid 1 first. A stranger with no account cannot set the field |
| PHP RCE / LPE | n/a | Closed | This leftover is browser JS, not PHP on the server |
Same pin leftover: Move Block sibling markup.
I pinned drupal/drupal 11.4.8 after SA-CORE-2026-011. Five hunts: XSS/HTMX, file/MIME/oEmbed, JSON:API/unserialize/SQLi, auth/CSRF/Host, REST/media/Views. JSON:API access, EntityQuery IN keys (SA-004), serialized-field POI (SA-005), and rebuild.php Host (SA-007) stay patched. The HTMX skip list landed in Xss.php only.
$skip = (
in_array($attribute_name, ['style', 'srcdoc']) ||
str_starts_with($attribute_name, 'on') ||
str_starts_with($attribute_name, 'data-hx') ||
str_starts_with($attribute_name, 'hx') ||
str_starts_with($attribute_name, '-') ||
strlen($attribute_name) > 96
);
AttributeXss.php leftover:
$skip = (
$attribute_name == 'style' ||
str_starts_with($attribute_name, 'on') ||
str_starts_with($attribute_name, '-') ||
strlen($attribute_name) > 96
);
Callers include LinkFormatter.php and MenuLinkContent::getUrlObject().
Wrong turns already recorded: treating Xss::filter as the leftover (it is the control); unauth JSON:API write; PHP RCE; a reverse shell. Theatre. The oracle is the attr remaining on the node HTML plus core/htmx loaded.
python3 drupal-htmx-attr-Abraxas-Labs.py
or
cd lab
./run.sh
Target only http://127.0.0.1:19480. Live drupal:11.4.8-apache, Standard profile, JSON:API write enabled for the lab. Compose project drupal-htmx-attr.
SUCCESS drupal-htmx-attr how=live write=jsonapi nid=1 hx-on-click=kept jsonapi-stored=1 xss-filter=stripped htmx=loaded marker=1 version=11.4.8 DRUPAL-HTMX-ATTR-WITNESS
Copy the hx / data-hx / srcdoc denylist into AttributeXss. Fold it into Xss as the class docblock already says.
61044508071749629aaf3380aa85971e94df9e84)AttributeXss.php · Xss.phpGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.