
Drupal 11.4.8 - High - Remote - XSS - Move Block sibling labels skip @label escaping
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-move-block-markup
Class: Stored XSS Reach: Remote
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-012 switched the current Move Block row to t('@label (current)'), which Html::escape()s. Sibling rows in the same region still assign BlockPluginTrait::label() straight to #markup in MoveBlockForm. That label is configuration['label'], a raw textfield. Renderer::ensureMarkupIsSafe then XSS-filters a string with the admin tag list, so a <b> (and other admin tags) survives.
A Layout Builder editor who can set a block title to HTML reaches another editor or admin who opens Move on a different block in that region. Stored XSS in the Move dialog. Not RCE and not LPE.
| ID | no CVE yet |
| Class | Stored XSS |
| Reach | Remote (Layout Builder block configure; victim opens Move Block) |
| CWE | CWE-79 |
| CVSS | Medium: 5.4 CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N |
| Product | Drupal core |
| Affected | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Auth | Layout Builder block configure. Victim opens Move Block. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Writer: user who can place or configure Layout Builder blocks (not site-config). Victim: another LB editor or admin who opens Move on a different block in the same region.
| Privilege | Method / sink | Kind | Lab-proven or inferred |
|---|---|---|---|
| Print attacker HTML in the Move dialog sibling row | $label['#markup'] = $plugin->label(); then ensureMarkupIsSafe | Integrity of HTML | Lab-proven. Sibling HTML is <b class="lb-sib">DRUPAL-MOVE-BLOCK-MARKUP-WITNESS</b> |
Keep admin-tag markup (<b>, and the rest of Xss::getAdminTagList) | string #markup is XSS-filtered with the admin list, not escaped | Integrity of HTML | Lab-proven for <b> |
| Session / actions as the victim editor in the Layout Builder origin | Stored XSS in that dialog | Integrity of victim session | Inferred. Lab stops at raw HTML in the sibling cell |
| Negative: current row | t('@label (current)', ...) | Closed | Lab-proven. <em class="lb-cur">... |
Negative: sibling through @label | patched gadget path | Closed | Lab-proven. <b class="lb-sib">... |
Closed on this leftover: live Drupal site install (this lab is a gadget); Layout Builder UI session theft as a measured HTTP oracle; RCE; LPE. Unauthenticated visitors cannot open Move Block.
Same pin leftover: AttributeXss HTMX attrs.
I pinned Drupal 11.4.8 after SA-CORE-2026-012. The advisory closed the current block title. MoveBlockForm.php 210-218:
if ($is_current_block) {
$label['#markup'] = $this->t('@label (current)', ['@label' => $plugin->label()]);
// ...
}
else {
$label['#markup'] = $plugin->label();
}
BlockPluginTrait::label() returns configuration['label'] when set. Renderer::ensureMarkupIsSafe does not XSS-filter MarkupInterface (@label already escaped). A raw string is filtered with the admin tag list.
Wrong turns already recorded: treating the current row as still raw (it is not); claiming this is RCE; a live Drupal site as the only oracle (the leftover is these functions); a reverse shell. Theatre.
python3 drupal-move-block-markup-Abraxas-Labs.py
or
cd lab
./run.sh
Target only http://127.0.0.1:19490. Faithful gadget of the 11.4.8 functions (copy of MoveBlockForm leftover, FormattableMarkup, ensureMarkupIsSafe, Xss::filter). Not a live drupal:11.4.8 site. Last line uses how=gadget.
SUCCESS drupal-move-block-markup how=gadget sibling-raw-tag=yes current-escaped=yes patched-sibling-escaped=yes DRUPAL-MOVE-BLOCK-MARKUP-WITNESS
Use @label (or Html::escape) for every row, not only the current block.
61044508071749629aaf3380aa85971e94df9e84)MoveBlockForm.php · BlockPluginTrait.phpGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.