サーバーサイドリクエストフォージェリ

C#のサーバーサイドリクエストフォージェリ

説明

ユーザーが指定したURLを、HttpClient、WebRequest、Uri を使うAPIなどによるサーバー側のリクエストに渡すと、内部サービスやメタデータのエンドポイントにリクエストを送らされるおそれがあります。

想定される影響

  • 内部ネットワークの調査、クラウドのメタデータの窃取、内部APIの呼び出しにつながる可能性があります。

対処方法

URLやホストをユーザーから直接受け取らず、サーバーが管理する識別子と固定URIの対応表から接続先を選ぶ方法を優先します。任意のURLが必須なら、スキーム・ホスト・ポートを許可リストで厳密に制限し、DNS応答のすべてのIPについてループバック、プライベート、リンクローカル、メタデータのアドレスを拒否します。また、自動リダイレクトを無効にするか、各リダイレクト先を再検証してください。

例

変更前

csharp
var response = await httpClient.GetAsync(url);

変更後

csharp
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

public sealed class StatusController : ControllerBase
{
    private static readonly IReadOnlyDictionary<string, Uri> Endpoints =
        new Dictionary<string, Uri>(StringComparer.Ordinal)
        {
            ["catalog"] = new Uri("https://catalog-api.example.com/health"),
            ["billing"] = new Uri("https://billing-api.example.com/health")
        };

    private static readonly HttpClient Client = new HttpClient(
        new HttpClientHandler { AllowAutoRedirect = false });

    [HttpGet("/status")]
    public async Task<IActionResult> GetStatus([FromQuery] string service)
    {
        if (service == null || !Endpoints.TryGetValue(service, out Uri endpoint))
        {
            return BadRequest("Unknown service");
        }

        using HttpResponseMessage response = await Client.GetAsync(endpoint);
        return StatusCode((int)response.StatusCode);
    }
}

説明:

  • 変更前: ユーザーが制御するURLを外部へのリクエストAPIに渡すと、SSRFにつながるおそれがあります。
  • 変更後: ユーザーが選べるのはサービス識別子だけで、実際のURIはサーバーコード内の固定の対応表から取得します。不明な識別子は拒否し、自動リダイレクトも無効にしているため、入力やリダイレクトから別の接続先を指定できません。

参考資料