Mittel

ApostropheCMS: Host-Header steuert Ursprung serverseitiger Bildabrufe in @apostrophecms/ai-helper (SSRF)

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

Zusammenfassung

Das Paket @apostrophecms/ai-helper erstellt die URL für das serverseitige Abrufen von Bildern, indem ein speicherrelativer Pfad gegenüber req.baseUrl aufgelöst wird. Wenn die Website keine globale Basis-URL festlegt (Standardeinstellung), wird req.baseUrl aus dem eingehenden HTTP-Header Host abgeleitet. Ein authentifizierter Editor kann ein Base64-KI-Bild generieren (das ohne gespeichertes url persistiert wird) und anschließend eine unautorisierte PATCH-Anforderung zum Akzeptieren mit einem vom Angreifer gewählten Host-Header senden. Der Server sendet daraufhin einen ausgehenden GET an den vom Angreifer ausgewählten Ursprung, was eine Server-Side Request Forgery (SSRF) ermöglicht und Hosts sowie Ports erreicht, die der Client ansonsten nicht kontaktieren könnte. Der betroffene Code befindet sich in packages/ai-helper/modules/@apostrophecms/ai-helper-image/index.js.

Ursache

Die Route zur Bildannahme stuft req.baseUrl fälschlicherweise als sicheren Ursprung für die Konstruktion eines internen Abrufziels ein; req.baseUrl wird jedoch vom Angreifer beeinflusst, wenn keine Basis-URL konfiguriert ist. Der URL-Builder verkettet einen relativen lokalen Standardspeicherpfad (z. B. /uploads) und löst ihn gegen req.baseUrl auf. Da der linke Operand relativ ist, stammt der resultierende Ursprung vollständig aus req.baseUrl. Zwei an anderer Stelle vorhandene Schutzmechanismen fehlen hier: Während die Generierungs-Route eine Bearbeitungsberechtigung erzwingt, führt die Akzeptierungs-Route weder eine Autorisierungs- noch eine Eigentumsprüfung durch, und es gibt vor dem Abruf weder eine Zulassungsliste noch eine Validierung des aufgelösten Ursprungs.

packages/ai-helper/modules/@apostrophecms/ai-helper-image/index.js
  aiHelperImageUrl (line 91-92): image.url || new URL(self.uploadfs.getUrl() + `/ai-helper-images/${image._id}.png`, req.baseUrl)
  aiHelperFetchImage (line 64-65): fetch(self.aiHelperImageUrl(req, image))
  apiRoutes.patch['ai-helper/:_id'] (line 314-333): findOne({ _id }) only; no permission or ownership check
packages/apostrophe/modules/@apostrophecms/i18n/index.js
  setPrefixUrls (line 799-820): req.baseUrl falls back to `${req.protocol}://${host}`, host from the Host header when apos.baseUrl is unset

Auswirkungen

Ein Angreifer mit einem gering privilegierten Editor-Konto (der erforderlichen Berechtigung zum Erzeugen eines KI-Bilds, wodurch ein Base64-Datensatz ohne gespeichertes url angelegt und dessen Kennung zurückgegeben wird) kann den Server dazu verleiten, ausgehende GET-Anfragen an einen beliebigen Ursprung zu senden, indem er in der Akzeptierungs-Anfrage einen manipulierten Host-Header übergibt. Dies führt zu SSRF: Interne Hosts und Ports, die aus der Netzwerkposition des Angreifers nicht erreichbar sind, können gescannt werden, und es ist eine begrenzte semi-blinde Exfiltration möglich, wenn eine als Bild interpretierbare Antwort am festen Pfad als sichtbarer Anhang bereitgestellt wird. Der Pfad ist auf /uploads/ai-helper-images/<id>.png festgelegt, sodass nur der Ursprung (Host, Port und http-Schema) kontrolliert werden kann; der Abruf beliebiger Pfade (wie das Auslesen von Cloud-Metadaten) ist über dieses Primitiv nicht möglich. Es wurden keine Integritäts- oder Verfügbarkeitsauswirkungen nachgewiesen. Für die Ausnutzung ist die Standardinstallation erforderlich, bei der keine globale Basis-URL definiert ist und lokaler Speicher genutzt wird, zusammen mit einem konfigurierten Provider, der Base64-Bilddaten liefert.

Proof of Concept

Preconditions:
  - ApostropheCMS site with @apostrophecms/ai-helper installed and a configured
    base64-returning image provider.
  - No global base URL configured (apos.baseUrl unset), default local uploadfs.
  - Attacker holds an account with edit capability on at least one content type.
  1. Als Editor authentifizieren und ein Base64-KI-Bild erzeugen: POST /api/v1/@apostrophecms/image/ai-helper mit dem Payload {"prompt":"x"}. Die Antwort enthält die neue Datensatz-ID <_id>. Der persistierte Datensatz besitzt kein url-Feld.
  2. Senden der Akzeptierungs-Anfrage mit einem bösartigen Host-Header:
PATCH /api/v1/@apostrophecms/image/ai-helper/<_id> HTTP/1.1
Host: 169.254.169.254
Content-Type: application/json

{"accepted": true}
  1. Der Server leitet req.baseUrl als http://169.254.169.254 ab, baut das Ziel http://169.254.169.254/uploads/ai-helper-images/<_id>.png zusammen und sendet die ausgehende Anfrage.
Derivation confirmed against source logic:
  base64 record (no url) + Host: 169.254.169.254
    -> new URL("/uploads/ai-helper-images/<_id>.png", "http://169.254.169.254")
    -> server fetches: http://169.254.169.254/uploads/ai-helper-images/<_id>.png
  control (apos.baseUrl set to https://cms.example.com):
    -> fetch target uses https://cms.example.com (Host ignored)
  control (record stores its own url):
    -> aiHelperImageUrl short-circuits to the stored url (no SSRF)

Angriffskette

  1. Exposition: Das Modul @apostrophecms/ai-helper-image registriert die Routen POST und PATCH unter /api/v1/@apostrophecms/image/ai-helper, die für jede authentifizierte Sitzung erreichbar sind, wenn das Paket installiert ist.
  2. Kontrolle: Der Angreifer kontrolliert den HTTP-Host-Header in der PATCH-Anfrage; die globale Middleware locale kopiert diesen nach req.baseUrl, da apos.baseUrl nicht gesetzt ist (packages/apostrophe/modules/@apostrophecms/i18n/index.js:805,816-819).
  3. Pfad: Der Angreifer generiert zunächst ein Base64-Bild über POST, wodurch ein Datensatz ohne url (ai-helper-image/index.js:282-284) gespeichert wird. Die zurückgegebene Kennung wird dann in der PATCH-Akzeptierungs-Anfrage verwendet, welche aiHelperFetchImage aufruft (:332).
  4. Schutzmechanismus: Der PATCH-Handler prüft lediglich req.body.accepted und die Existenz des Datensatzes über findOne({ _id }) (:318,:322). Es erfolgt keine Berechtigungsprüfung und keine Eigentümer-Eingrenzung, anders als bei der POST-Route, die checkPermissions aufruft (:176). Vor dem Abruf findet keine Validierung des aufgelösten Ursprungs statt.
  5. Primitiv: aiHelperImageUrl löst den relativen Speicherpfad gegen den aus dem Host abgeleiteten req.baseUrl auf und aiHelperFetchImage führt fetch auf das Ergebnis aus (:91-92,:64-65).
  6. Ergebnis: Der Server führt eine ausgehende GET an den gewählten Ursprung mit festem Pfad aus, was Zugriff auf interne Hosts und Ports sowie eine eingeschränkte Abfrage von Antworten ermöglicht.

Umgehungsanalyse

Die Überprüfung checkPermissions (ai-helper-image/index.js:176) der Generate-Route ist in der Accept-Route nicht vorhanden, weshalb der Abruf ungeschützt bleibt. Folgende Gegenargumente wurden geprüft und widerlegt:

  • Standardmäßiger lokaler Speicher gibt eine relative URL zurück: uploadfs.getUrl() ergibt /uploads, da das lokale Backend uploadsUrl = (apos.baseUrl || '') + prefix + '/uploads' erstellt; daher übernimmt new URL(relative, req.baseUrl) den Ursprung vollständig von req.baseUrl. Das Konfigurieren einer Basis-URL oder einer absoluten Speicher-URL (S3/CDN) verhindert den Angriff; dies sind jedoch Gegenmaßnahmen und nicht das Standardverhalten.
  • Der Base64-Datensatz enthält kein persistiertes url: insertOne(image) speichert den Datensatz, bevor image.url im speicherinternen Antwortobjekt zugewiesen wird (:282-284 gegenüber :294). Der PATCH-Handler liest den Datensatz daher ohne url ein, und der Abbruch in aiHelperImageUrl greift nicht.
  • Eigentum und Aufzählung: GET und DELETE dieser Datensätze sind auf userId beschränkt und Cuid2-Kennungen sind nicht erratbar; somit ist der praktische Akteur der generierende Editor. Das erforderliche Privileg beschränkt sich daher auf einen gering privilegierten authentifizierten Benutzer.
  • Kein Duplikat: Die Behebung der Core-Pretty-URL-SSRF-Meldung liegt vollständig im separaten Modul @apostrophecms/file und berührt packages/ai-helper nicht; die Rich-Text-Import-SSRF-Meldung verwendet eine andere Route mit Hostnamen-Zulassungsliste. Für @apostrophecms/ai-helper existiert keine vorherige Meldung. Es handelt sich um eine separate Schwachstelle.

Betroffene Versionen

  • Ecosystem: npm
  • Package: @apostrophecms/ai-helper
  • Confirmed affected range: >= 1.0.0-beta.10, <= 1.0.0-beta.12
  • Latest release checked: 1.0.0-beta.12 (npm registry, dist-tag latest)
  • Fix status: not fixed

Das betroffene Muster wurde in 1.0.0-beta.10 eingeführt; frühere Versionen bis 1.0.0-beta.9 riefen die gespeicherte Provider-URL direkt ab (fetch(url)) ohne req.baseUrl-Auflösung und sind daher nicht betroffen. Eine Überprüfung der Tarballs für 1.0.0-beta.10, 1.0.0-beta.11 und 1.0.0-beta.12 bestätigt, dass die vollständige Kette vorhanden ist. Das Paket wird nur über den Vorabveröffentlichungs-Kanal (Beta) verteilt.

Vorgeschlagene Behebung

req.baseUrl darf nicht als vertrauenswürdiger Ursprung für serverseitige Abrufe behandelt werden. Auf der Accept-Route sollte dieselbe Autorisierung wie auf der Generate-Route durchgesetzt werden, ergänzt um eine Eigentums- oder Bereichsprüfung. Unabhängig davon sollte das Ziel beschränkt werden: Der speicherrelative Pfad muss gegen eine vertrauenswürdige, serverseitig konfigurierte Basis aufgelöst werden anstatt gegen das anfragebasierte req.baseUrl, und der resultierende Ursprung sollte validiert oder über eine Zulassungsliste gefiltert werden, bevor fetch aufgerufen wird. Dies muss sowohl für Base64-Darstellungen als auch für Darstellungen mit gespeicherter URL sowie für Aufrufe von aiHelperImageUrl oder aiHelperFetchImage gelten. Als Zwischenlösung ohne Codeänderung kann eine globale Basis-URL festgelegt und Egress-Kontrollen auf dem Server eingerichtet werden.

Gemeldet von zx (GitHub: @manus-pi).

Was zu tun ist

Aktualisieren Sie auf Version 1.0.0-beta.13 oder höher. Als Übergangslösung kann eine globale Basis-URL konfiguriert und der ausgehende Netzwerkverkehr des Servers eingeschränkt werden.

Betroffene Software

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

PlattformPaketAnfällige VersionenBehoben in
npm-Paket @apostrophecms/ai-helper >= 1.0.0-beta.10, < 1.0.0-beta.13 1.0.0-beta.13

Verweise

CVSS-Wert 4.3/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
Schwäche (CWE)
CWE-918
Plattform
npm
Paket
@apostrophecms/ai-helper
Veröffentlicht
20.10.2026
Aktualisiert
20.10.2026 21:29

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

OSV / GitHub Advisory Database - GHSA-527g-ff96-x6wj - CC BY 4.0

Schwachstellenhistorie: @apostrophecms/ai-helper

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.

Dies ist die einzige bekannte Schwachstelle dieses Pakets.

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.