Next.jsのServer ActionsでSSRFが起こせるという話が話題になった
SSRFに馴染みがなかったので、まずはそれから調べた
SSRFとは
SSRF (Server-Side Request Forgery) とは、攻撃者がサーバー側のアプリケーションにリクエストを送信させることを目的とする攻撃手法のこと
これにより、攻撃者は本来アクセスできない内部システムやリソースにアクセスできる可能性がある
SSRFの仕組み
SSRFは、サーバーが外部のリソースにアクセスする機能を悪用する
例えば、あるウェブアプリケーションがユーザーからの入力に基づいて外部のURLにリクエストを送信する機能を持っている場合、攻撃者はその入力を操作してサーバーに任意のリクエストを送信させることができる
SSRF攻撃の例
- 内部ネットワークへのアクセス:
- 攻撃者がサーバーを利用して内部ネットワーク内の他のサーバーやサービスにアクセスすることができる
- これにより、内部のAPIエンドポイントや管理インターフェースにアクセスされる可能性がある
- メタデータの取得:
- 外部サービスへの不正アクセス:
今回のNext.jsのSSRFの問題
今回のNext.jsのSSRFの問題は、CVEに登録されていて CVE-2024-34351 となっている CVE - CVE-2024-34351
CVE-2024-34351 はServer Actionを利用している場合に、httpヘッダーの Host を改ざんしてリクエストすると、self hostingなNext.jsサーバーから任意のhttpリクエストを送信できてしまう脆弱性らしい
Next.jsのソースを見ると、Server Actionを呼び出してリダイレクトでレスポンスすると、次の関数が呼び出されている
async function createRedirectRenderResult( req: IncomingMessage, res: ServerResponse, redirectUrl: string, basePath: string, staticGenerationStore: StaticGenerationStore ) { res.setHeader('x-action-redirect', redirectUrl) // if we're redirecting to a relative path, we'll try to stream the response if (redirectUrl.startsWith('/')) { const forwardedHeaders = getForwardedHeaders(req, res) forwardedHeaders.set(RSC_HEADER, '1') const host = req.headers['host'] const proto = staticGenerationStore.incrementalCache?.requestProtocol || 'https' const fetchUrl = new URL(`${proto}://${host}${basePath}${redirectUrl}`) // .. snip .. try { const headResponse = await fetch(fetchUrl, { method: 'HEAD', headers: forwardedHeaders, next: { // @ts-ignore internal: 1, }, }) if ( headResponse.headers.get('content-type') === RSC_CONTENT_TYPE_HEADER ) { const response = await fetch(fetchUrl, { method: 'GET', headers: forwardedHeaders, next: { // @ts-ignore internal: 1, }, }) // .. snip .. return new FlightRenderResult(response.body!) } } catch (err) { // .. snip .. } } return RenderResult.fromStatic('{}') }
ここでhostがクライアントから取得されているので、ホストヘッダーを偽造すると、確かに任意のhttpリクエストを送信できてしまう
その後のロジックにより、特定の条件でしか発生しないようだが、確かにSSRFができてしまうようだ
気になるのは、redirectの処理内でfetchをしているところで、なんでこんなことをしているかというのは@akfmさんの記事で言及されていて面白かった
私もこれしかやり方ってないのかな?と思うが、今のところこれしかないんだろう
問題自体はv14.1.1にvupすれば解決する