ApostropheCMS: Host-Header steuert Ursprung serverseitiger Bildabrufe in @apostrophecms/ai-helper (SSRF)
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.
- Als Editor authentifizieren und ein Base64-KI-Bild erzeugen:
POST /api/v1/@apostrophecms/image/ai-helpermit dem Payload{"prompt":"x"}. Die Antwort enthält die neue Datensatz-ID<_id>. Der persistierte Datensatz besitzt keinurl-Feld. - 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}
- Der Server leitet
req.baseUrlalshttp://169.254.169.254ab, baut das Zielhttp://169.254.169.254/uploads/ai-helper-images/<_id>.pngzusammen 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
- Exposition: Das Modul
@apostrophecms/ai-helper-imageregistriert die RoutenPOSTundPATCHunter/api/v1/@apostrophecms/image/ai-helper, die für jede authentifizierte Sitzung erreichbar sind, wenn das Paket installiert ist. - Kontrolle: Der Angreifer kontrolliert den HTTP-
Host-Header in derPATCH-Anfrage; die globale Middlewarelocalekopiert diesen nachreq.baseUrl, daapos.baseUrlnicht gesetzt ist (packages/apostrophe/modules/@apostrophecms/i18n/index.js:805,816-819). - Pfad: Der Angreifer generiert zunächst ein Base64-Bild über
POST, wodurch ein Datensatz ohneurl(ai-helper-image/index.js:282-284) gespeichert wird. Die zurückgegebene Kennung wird dann in derPATCH-Akzeptierungs-Anfrage verwendet, welcheaiHelperFetchImageaufruft (:332). - Schutzmechanismus: Der
PATCH-Handler prüft lediglichreq.body.acceptedund die Existenz des Datensatzes überfindOne({ _id })(:318,:322). Es erfolgt keine Berechtigungsprüfung und keine Eigentümer-Eingrenzung, anders als bei derPOST-Route, diecheckPermissionsaufruft (:176). Vor dem Abruf findet keine Validierung des aufgelösten Ursprungs statt. - Primitiv:
aiHelperImageUrllöst den relativen Speicherpfad gegen den aus dem Host abgeleitetenreq.baseUrlauf undaiHelperFetchImageführtfetchauf das Ergebnis aus (:91-92,:64-65). - Ergebnis: Der Server führt eine ausgehende
GETan 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 BackenduploadsUrl = (apos.baseUrl || '') + prefix + '/uploads'erstellt; daher übernimmtnew URL(relative, req.baseUrl)den Ursprung vollständig vonreq.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, bevorimage.urlim speicherinternen Antwortobjekt zugewiesen wird (:282-284gegenüber:294). DerPATCH-Handler liest den Datensatz daher ohneurlein, und der Abbruch inaiHelperImageUrlgreift nicht. - Eigentum und Aufzählung:
GETundDELETEdieser Datensätze sind aufuserIdbeschrä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/fileund berührtpackages/ai-helpernicht; die Rich-Text-Import-SSRF-Meldung verwendet eine andere Route mit Hostnamen-Zulassungsliste. Für@apostrophecms/ai-helperexistiert keine vorherige Meldung. Es handelt sich um eine separate Schwachstelle.
Betroffene Versionen
Ecosystem: npmPackage: @apostrophecms/ai-helperConfirmed affected range: >= 1.0.0-beta.10, <= 1.0.0-beta.12Latest 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.
| Plattform | Paket | Anfällige Versionen | Behoben in |
|---|---|---|---|
| npm-Paket | @apostrophecms/ai-helper | >= 1.0.0-beta.10, < 1.0.0-beta.13 | 1.0.0-beta.13 |
Verweise
- https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-527g-ff96-x6wj
- https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
- https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
- https://github.com/apostrophecms/apostrophe
- https://github.com/apostrophecms/apostrophe/releases/tag/@apostrophecms/ai-helper@1.0.0-beta.13
- 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.