Ghost: Schlagwortname bricht aus den strukturierten Daten aus

Die Rolle Autor platziert Skript im Namen eines Schlagworts, das ungefiltert in den strukturierten Daten jeder Beitragsseite landet.

Advisory-ID: TP-2026-084
Produkt: Ghost (Publishing-Plattform fuer Blogs und Newsletter, Node.js)
Schwachstellentyp: Stored Cross-Site-Scripting (CWE-79)
CVE: CVE-2026-103292
CVSS 3.1: 8.0 (Hoch) · CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H
Betroffene Versionen: >= 0.5.3, < 6.50.0
Hersteller-Advisory: GHSA-mp9q-4q6m-44f5
Behoben in: 6.50.0
Gemeldet: 03. Juni 2026

Zusammenfassung

Ghost legt zu jeder Seite einen Block mit strukturierten Daten in den Seitenkopf, damit Suchmaschinen Titel, Schlagworte und Autor auswerten koennen. Die Namen der Schlagworte landen dort ungefiltert, obwohl jedes benachbarte Feld desselben Blocks maskiert wird. Ein Autor, also die niedrigste Rolle mit Schreibrecht auf Schlagworte, legt ein Schlagwort an, dessen Name die Zeichenfolge zum Schliessen des Skriptblocks enthaelt, und haengt es an einen veroeffentlichten Beitrag. Jeder anonyme Besucher der Schlagwortseite und jeder Besucher eines Beitrags mit diesem Schlagwort fuehrt daraufhin Skript des Angreifers aus, denn die oeffentliche Seite sendet keine Content-Security-Policy. Oeffnet ein angemeldeter Administrator einen solchen Beitrag, reitet das Skript auf seiner Sitzung in die Verwaltungs-API und legt dort einen dauerhaften Admin-Schluessel an.

Ursache

getPostSchema schreibt die Schlagwortnamen eines Beitrags als keywords in das Schema, ohne sie durch escapeExpression zu schicken (core/frontend/meta/schema.js:128), und getTagSchema verfaehrt mit dem Namen der Schlagwortseite ebenso (:160), waehrend die uebrigen Namens- und Beschreibungsfelder derselben Datei maskiert werden (:30, :89, :114, :123, :175). Der Helfer ghost_head serialisiert das Schema mit JSON.stringify in einen script-Block vom Typ application/ld+json (core/frontend/helpers/ghost_head.js:325-328) und gibt das Ergebnis als SafeString zurueck (:430), weshalb Handlebars nicht nachtraeglich maskiert. JSON.stringify maskiert nur Anfuehrungszeichen, nicht die spitzen Klammern, sodass ein </script> im Namen den Block beendet und der HTML-Parser das Folgende als Skript liest. Die Namenspruefung fuer Schlagworte untersagt allein ein fuehrendes Komma (core/server/data/schema/schema.js:272), und models/tag.js:101 erzeugt daraus nur den Slug, waehrend der Name unveraendert gespeichert wird. Die Rolle Autor traegt ab Werk das Recht, Schlagworte anzulegen, und die oeffentliche Seite sendet weder CSP noch X-Frame-Options noch nosniff.

Proof of Concept

# 1. als Autor ein Schlagwort anlegen, Name:
News</script><script>alert(document.domain)</script>

# 2. an einen veroeffentlichten Beitrag haengen, dann anonym abrufen:
curl -s http://ghost.example/tag/news/   | grep -A2 '"name"'
curl -s http://ghost.example/mein-beitrag/ | grep '"keywords"'

# Ausgabe, das </script> steht roh im ld+json-Block:
"keywords": "News</script><script>alert(document.domain)</script>",

Nur die Anfuehrungszeichen werden JSON-maskiert, die spitzen Klammern nicht. Der Skriptblock endet damit vorzeitig, und ohne CSP fuehrt der Browser das nachfolgende Skript aus. Die Variante ueber keywords wirkt auf jedem Beitrag mit diesem Schlagwort, nicht nur auf der Schlagwortseite.

Auswirkung

  • Skriptausfuehrung in der Herkunft der Website bei jedem anonymen Besucher einer betroffenen Beitrags- oder Schlagwortseite.
  • Uebernahme der Instanz, wenn ein angemeldeter Administrator eine solche Seite oeffnet: die Verwaltung liegt unter derselben Herkunft, das Sitzungs-Cookie wird automatisch mitgesendet, und die Herkunftspruefung als einziger CSRF-Schutz ist bei gleicher Herkunft erfuellt. Ein POST auf /ghost/api/admin/integrations/ legt einen dauerhaften Admin-Schluessel an, der eine Abmeldung ueberlebt.
  • Rechteausweitung von der Rolle Autor zu Eigentuemerrechten, ohne dass ein Beitrag freigegeben werden muss.
  • Auslesen der Mitgliederdaten und Veraenderung der Einstellungen ueber dieselbe mitgerittene Sitzung.

Referenzen

Steckt so etwas in Ihrer Software?

Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.