Mittel

ApostropheCMS: Clientseitige Prototype Pollution in apos.http.parseQuery führt zu DOM-basiertem XSS gegen authentifizierte Benutzer

  • Veröffentlicht: 20.10.2026
  • Aktualisiert: 20.10.2026 21:34

Zusammenfassung

Die clientseitige Hilfsfunktion apos.http.parseQuery() in ApostropheCMS erstellt ein geschachteltes Objekt aus einem URL-Query-String, ohne die Schlüssel __proto__, constructor oder prototype zu bereinigen. Das Parsen eines vom Angreifer kontrollierten Query-Strings schreibt daher beliebige Eigenschaften auf Object.prototype (clientseitige Prototype Pollution).

Die Administrationsoberfläche von ApostropheCMS ruft diesen Parser mit window.location.search bei jedem Laden einer authentifizierten Seite auf (der Initialisierer data() der Admin-Kontextleiste). Da die Administrationsoberfläche auf Vue basiert, wird eine manipulierte, aufzählbare Eigenschaft auf Object.prototype von Vues standardmäßiger Element-Mounting-Routine übernommen und als Element-Eigenschaft in das DOM geschrieben. Dadurch wird die Prototype Pollution zu einem DOM-basierten XSS in der authentifizierten Sitzung des Opfers. Das Eskalations-Gadget ist ein bekanntes, allgemeines Vue-Verhalten; die zu meldende Schwachstelle besteht in der unbereinigten Quelle parseQuery zusammen mit dem authentifizierten Trigger, beides Bestandteile von ApostropheCMS-Code.

Jeder Benutzer, der einen beliebigen angemeldeten Benutzer (von der Rolle guest mit den geringsten Rechten bis hin zur Rolle admin mit den höchsten Rechten) dazu bringen kann, eine manipulierte URL zu öffnen, kann beliebigen JavaScript-Code in dessen Sitzung ausführen.

Dies ist verschieden von CVE-2026-53609 (serverseitige Prototype Pollution über apos.util.set() / $pullAll, behoben in 4.31.0): andere Seite (Client vs. Server), anderer Codepfad, und der betroffene Code ist im aktuellen Release vorhanden.

Paket

  • Ökosystem: npm
  • Paket: apostrophe
  • Betroffene Versionen: alle 4.x-Releases, 4.0.0 bis 4.32.2 (aktuell latest). Verifiziert durch Inspektion der veröffentlichten npm-Tarballs: apostrophe@4.0.0 enthält bereits den fehlerhaften Abstieg (parentKey = 'root') ohne __proto__/constructor/prototype-Filterung, und apostrophe@4.32.2 ist in dieser Hinsicht unverändert. Die betroffene parseQuery()-Logik wurde in Commit 0255086bf (2020-11-12) eingeführt, deutlich vor der Version 4.0, sodass ApostropheCMS 3.x ebenfalls betroffen ist (derselbe Code wird in 3.x unter dem älteren Build-Pfad modules/@apostrophecms/util/ui/public/2-http.js ausgeliefert).
  • Behobene Versionen: Zum Zeitpunkt der Erstellung keine.

Details

Das Problem besteht aus drei Teilen. Die Teile 1 und 2 stammen aus dem ApostropheCMS-Code und bilden die zu meldende Schwachstelle. Teil 3 ist das Eskalations-Gadget, das Standardverhalten von Vue darstellt und der Vollständigkeit halber aufgeführt wird.

1. Das Pollution-Primitiv: Quelle (ApostropheCMS)

modules/@apostrophecms/util/ui/src/http.js, Funktion apos.http.parseQuery() (Zeile ~301) und deren inneres patch() (Zeilen ~317–354):

js
function patch(key, value) {
  let match;
  let parentKey = 'root';
  let context = data;                    // data = {}  (ordinary object)
  key = decodeURIComponent(key);
  const path = key.split('[');
  path.forEach(function(subKey) {
    if (subKey === ']') {
      /* array handling */
    } else if (subKey.match(/^\d+]/)) {
      /* numeric index handling */
    } else {
      match = subKey.replace(']', '');
      if (!context[parentKey]) {         // (A) truthy check, not an own-property check
        context[parentKey] = {};
      }
      context = context[parentKey];      // (B) descends into whatever this resolves to
      parentKey = match;
    }
  });
  value = (value === null) ? value : decodeURIComponent(value);
  if (Array.isArray(context[parentKey])) {
    context[parentKey].push(value);
  } else if (context[parentKey] !== undefined) {
    context[parentKey] = [ context[parentKey], value ];
  } else {
    context[parentKey] = value;          // (C) assignment onto the resolved object
  }
}

Kein Schlüssel wie __proto__, constructor oder prototype wird abgewiesen. Beim Durchlaufen der Eingabe __proto__[x]=v:

  1. Segment __proto__: Bei (A) löst sich context['__proto__'] zum geerbten Prototyp des Objekts auf (truthy), sodass der Initialisierer {} übersprungen wird; bei (B) wird context zu Object.prototype.
  2. Segment x: Bei (C), Object.prototype['x'] = v.

Die Truthy-Prüfung bei (A) (anstelle einer Own-Property-Prüfung) ermöglicht den Abstieg in den geerbten Prototyp. Da die Zuweisung bei (C) ein gewöhnlicher Schreibvorgang auf eine Eigenschaft ist, ist die resultierende Eigenschaft Object.prototype aufzählbar (enumerable), was für das Gadget wichtig ist (3).

2. Der Trigger: Wird bei jedem Laden einer authentifizierten Seite erreicht (ApostropheCMS)

modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue. Der Initialisierer data() der Komponente (Zeile ~64) wird ausgeführt, wenn die Admin-Kontextleiste geladen wird (d. h. bei jedem Aufruf einer authentifizierten Seite), und übergibt den unveränderten Query-String direkt an den betroffenen Parser:

js
data() {
  const query = apos.http.parseQuery(location.search);   // <- attacker-controlled -> (1) pollution, on mount
  const draftMode = query.aposMode || 'published';
  // ...
}

Die Methode refresh() derselben Komponente (Zeile ~606) übergibt window.location.search bei Aktualisierungsereignissen der Admin-Leiste erneut an parseQuery() und bindet es in doc._url ein (wird innerhalb von apos.http.remote() erneut geparst), was zusätzliche Auslösepunkte bietet.

Die Admin-Kontextleiste wird nur geladen, wenn eine Sitzung aktiv ist und das Admin-Bundle geladen wird; für anonyme Besucher wird dieser Code daher nicht ausgeführt, weshalb die Manipulation nur im angemeldeten Zustand beobachtet wird. All dies ist Core-Code von ApostropheCMS (node_modules/apostrophe/...) und stammt nicht aus einem Starter-Kit oder Projektinhalt.

3. Das Eskalations-Gadget: Vue Element Mount (Framework-Verhalten; bekannt, nicht neu)

Die demonstrierte Payload manipuliert die Eigenschaft innerHTML (__proto__[innerHTML]=<img/src/onerror=alert(1)>). Die Senke, die dies verarbeitet, ist Vues standardmäßiger Element-Mounting-Pfad, nicht ApostropheCMS-Code. Bestätigt im gebündelten Framework (vue / @vue/runtime-core / @vue/runtime-dom, alle 3.5.42):

@vue/runtime-core mountElement, Prop-Enumeration (Zeile ~5741):

js
if (props) {
  for (const key in props) {                              // for...in walks the prototype chain
    if (key !== "value" && !shared.isReservedProp(key)) { // isReservedProp does NOT list innerHTML
      hostPatchProp(el, key, null, props[key], ...);      // props["innerHTML"] → inherited polluted value
    }
  }

@vue/runtime-dom patchDOMProp (Zeile ~583), erreicht über hostPatchProp:

js
function patchDOMProp(el, key, value, ...) {
  if (key === "innerHTML" || key === "textContent") {
    if (value != null) {
      el[key] = key === "innerHTML" ? unsafeToTrustedHTML(value) : value;  // el.innerHTML = payload
    }
    return;
  }

Mechanismus: Wann immer Vue ein Element mountet, das ein props-Objekt ungleich null ohne eigenes innerHTML besitzt (üblich während des Admin-Renderings), enumeriert for (const key in props) das geerbte, aufzählbare innerHTML von Object.prototype; dies passiert die Filter "value" und isReservedProp; props["innerHTML"] löst sich zur Payload des Angreifers auf; und patchDOMProp schreibt es in el.innerHTML. unsafeToTrustedHTML ist Vues Trusted-Types-Wrapper und kein Sanitizer; es leitet Werte unverändert weiter, sofern Trusted Types nicht erzwungen wird (nicht standardmäßig aktiv).

Dieses Gadget ist eine gut dokumentierte Eskalation von clientseitiger Prototype Pollution zu XSS in Vue-Anwendungen. Es wird nicht als neuartige Entdeckung beansprucht und ist nicht durch ApostropheCMS zu beheben, da es ohne Änderungen an Vue nicht entfernt werden kann. Seine Relevanz liegt darin, dass es die Pollution (1) in jeder ApostropheCMS-Admin-Sitzung zuverlässig ausnutzbar macht.

Proof of Concept

Voraussetzung: Eine laufende ApostropheCMS-Instanz und eine beliebige authentifizierte Sitzung (ein guest-Konto genügt). Der Tester bzw. das Opfer muss angemeldet sein.

A. Prototype Pollution (in der Konsole überprüfbar):

  1. Als beliebiger Benutzer anmelden.
  2. Aufrufen: https://TARGET/?__proto__[prop]=polluted
  3. In der Browser-Konsole: Object.prototype.prop > gibt "polluted" zurück.

B. DOM-basiertes XSS:

  1. Als beliebiger Benutzer anmelden.
  2. Aufrufen: https://TARGET/?__proto__[innerHTML]=%3Cimg/src/onerror%3dalert(1)%3E
  3. alert(1) wird im authentifizierten Kontext des Opfers ausgeführt.

Um den Call-Stack der Senke nachzuvollziehen: Entwicklertools > Quellen > Admin-Bundle öffnen > Pretty-Print > nach unsafeToTrustedHTML (oder === "innerHTML") suchen > Zeilen-Breakpoint im innerHTML-Zweig von patchDOMProp setzen > Payload-URL im angemeldeten Zustand neu laden > value untersuchen und den Call-Stack über hostPatchProp bis zu for…in von mountElement zurückverfolgen.

Sowohl A als auch B wurden gegen ApostropheCMS 4.32.2 (Vue 3.5.42) in Chrome-, Firefox- und Edge-Browsern reproduziert.

Auswirkungen

Jede Partei, die einen authentifizierten ApostropheCMS-Benutzer dazu bringen kann, eine manipulierte URL zu öffnen, kann beliebigen JavaScript-Code in dessen Sitzung ausführen. Da das Eskalations-Gadget ein garantiertes Verhalten von Vue ist, handelt es sich um einen zuverlässigen Exploit und nicht um ein theoretisches Konstrukt. Die Konsequenzen hängen von der Rolle des Opfers ab:

  • Beliebiges authentifiziertes Opfer: Aktionen und Lesezugriffe, die diesem Konto zur Verfügung stehen; Diebstahl des Sitzungscookies / CSRF-Tokens aus dem authentifizierten Kontext.
  • Editor-/Admin-Opfer: Das injizierte Skript kann die authentifizierten REST-APIs zur Verwaltung als das Opfer aufrufen, Dokumente und Benutzerkonten erstellen oder bearbeiten, Konfigurationen ändern und möglicherweise zur Kontoübernahme sowie zur vollständigen Kompromittierung der CMS-Instanz führen.

Prototype Pollution kann zudem andere Codepfade beeinträchtigen, die geerbte Eigenschaften auslesen, sodass neben der gezeigten Vue-innerHTML-Senke weitere Gadgets existieren können.

Behebung

Die Behebung muss in ApostropheCMS an der Quelle (1) erfolgen. Da das Gadget (3) Framework-Verhalten ist und sich nicht verlässlich ändern lässt, ist die Bereinigung in parseQuery die korrekte Lösung.

Primäre Fehlerbehebung: Prototyp-beeinflussende Schlüssel in parseQuery/patch() abweisen:

js
apos.http.parseQuery = function(query) {
  query = query.replace(/^\?/, '');
  const data = {};
  const DANGEROUS = new Set([ '__proto__', 'constructor', 'prototype' ]);
  query.split('&').forEach(function(pair) {
    // ... unchanged ...
  });
  return data.root || {};

  function patch(key, value) {
    let match;
    let parentKey = 'root';
    let context = data;
    key = decodeURIComponent(key);
    const path = key.split('[');
    let dangerous = false;
    path.forEach(function(subKey) {
      const cleanKey = subKey.replace(']', '');
      if (DANGEROUS.has(cleanKey)) {   // refuse to traverse into prototype-affecting keys
        dangerous = true;
        return;
      }
      // ... existing subKey handling ...
    });
    if (dangerous) {
      return;                          // discard this parameter entirely
    }
    // ... existing assignment ...
  }
};

Tiefenstaffelung (Defense in depth):

  • Die Truthy-Prüfung bei (A) durch eine Own-Property-Prüfung ersetzen: if (!Object.prototype.hasOwnProperty.call(context, parentKey)), sodass ein Schlüssel niemals in einen geerbten Prototyp absteigen kann.
  • data und alle erstellten Container mit Object.create(null) initialisieren, damit Zwischenobjekte keine Prototypenkette besitzen.

Vorläufige Schutzmaßnahme für Administratoren: Den Kreis der authentifizierten Konten einschränken und beachten, dass das Öffnen nicht vertrauenswürdiger Links während einer aktiven Sitzung dies auslösen kann. Es gibt keine serverseitige Konfigurationsoption, um das betroffene clientseitige Verhalten zu deaktivieren.

Referenzen

  • Quelle (ApostropheCMS): apostrophe/modules/@apostrophecms/util/ui/src/http.js — parseQuery() / patch()
  • Trigger (ApostropheCMS): apostrophe/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue — data() (~Zeile 64), refresh() (~Zeile 606)
  • Eskalations-Gadget (Vue 3.5.42, Framework-Verhalten): @vue/runtime-core mountElement Prop-Enumeration (~Zeile 5741); @vue/runtime-dom patchDOMProp innerHTML-Zweig (~Zeile 583)
  • Verwandt, aber unabhängig: CVE-2026-53609 / GHSA-6h5j-32cf-4253 (serverseitige Prototype Pollution über apos.util.set() / $pullAll, behoben in 4.31.0)
  • CWE-1321; CWE-79

Danksagung

GitHub-Benutzername: Xx-LoGiCaL-xX

Was zu tun ist

Die Schwachstelle muss im ApostropheCMS-Code durch Bereinigung von parseQuery behoben werden.

Primäre Fehlerbehebung: Prototyp-beeinflussende Schlüssel in parseQuery/patch() abweisen:

js
apos.http.parseQuery = function(query) {
  query = query.replace(/^\?/, '');
  const data = {};
  const DANGEROUS = new Set([ '__proto__', 'constructor', 'prototype' ]);
  query.split('&').forEach(function(pair) {
    // ... unchanged ...
  });
  return data.root || {};

  function patch(key, value) {
    let match;
    let parentKey = 'root';
    let context = data;
    key = decodeURIComponent(key);
    const path = key.split('[');
    let dangerous = false;
    path.forEach(function(subKey) {
      const cleanKey = subKey.replace(']', '');
      if (DANGEROUS.has(cleanKey)) {   // refuse to traverse into prototype-affecting keys
        dangerous = true;
        return;
      }
      // ... existing subKey handling ...
    });
    if (dangerous) {
      return;                          // discard this parameter entirely
    }
    // ... existing assignment ...
  }
};

Defense-in-Depth-Maßnahmen:

  • Truthy-Prüfung bei (A) durch if (!Object.prototype.hasOwnProperty.call(context, parentKey)) ersetzen.
  • data und Container mit Object.create(null) erstellen.

Übergangsmaßnahmen: Kreis der authentifizierten Konten einschränken und keine nicht vertrauenswürdigen Links im angemeldeten Zustand aufrufen. Es existiert keine serverseitige Konfigurationsoption zum Deaktivieren des Verhaltens.

Betroffene Software

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

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

Verweise

CVSS-Wert 6.1/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Schwäche (CWE)
CWE-1321, CWE-79
Plattform
npm
Paket
apostrophe
Veröffentlicht
20.10.2026
Aktualisiert
20.10.2026 21:34

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

OSV / GitHub Advisory Database - GHSA-m2m5-rw3w-cwmj - 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 (27) im Archiv
Einträge insgesamt
27
Erste → letzte
09.2020 10.2026
Ohne Fix
4
In Angriffen ausgenutzt
0

Verteilung nach Schweregrad

  • Kritisch 2
  • Hoch 8
  • Mittel 13
  • 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: 23
Schweregrad:
  1. ApostropheCMS: Clientseitige Prototype Pollution in apos.http.parseQuery führt zu DOM-basiertem XSS gegen authentifizierte Benutzer dieser Eintrag
    Mittel · 6.1 CVE-2026-113659 < 4.33.0 4.33.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.