ASP.NET Core でクロスサイトリクエストフォージェリ (XSRF/CSRF) 対策を行う

ページ更新日 :
ページ作成日 :

環境

Visual Studio
  • Visual Studio 2019
ASP.NET Core
  • 3.0
  • 3.1

クロスサイトリクエストフォージェリとは

クロスサイトリクエストフォージェリ (CSRF/XSRF) とは本来対象となるサイト内のみで処理を行うものに対して、 外部のサイトなどから更新処理などを実行される脆弱性に対する攻撃のことを言います。

例えば、Web サイトでコメントを投稿する機能があったとします。 通常は該当のサイトからコメントを入力して投稿することになると思いますが、 この送信処理はほとんどの場合、対象の URL に対してデータを投げて処理するようになっています。

そのため、対象のサイト以外からその URL に対して同じようなデータを送信すれば、 コメントを投稿したのと同じ扱いになってしまいます。

攻撃者のみがこの行為を行うのであればあまり脅威にはなりませんが、 CSRF の怖いところは、攻撃者が偽装サイトを構築したり、隠し URL をサイトに埋め込むことによって 他ユーザーが意図せずにアクセスし攻撃者になってしまうところです。

ここでは深く説明はしませんので詳しくはクロスサイトリクエストフォージェリについて調べてみてください。

ASP.NET Core ではこの対策機能がフレームワークに組み込まれています。

クロスサイトリクエストフォージェリの動作を確認する

実際に外部のサイトやツールなどから更新処理が実行されるかを確認してみます。

Index.cshtml には入力したテキストをサーバーに送信する入力フォームを作成します。 そしてサーバーが入力テキストに合わせて作成したメッセージを表示するように ViewData を配置します。

Index.cshtml

@{
  ViewData["Title"] = "Home Page";
}

<div class="text-center">
  <h1 class="display-4">Welcome</h1>
  <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<form method="post">
  <input type="text" name="text" />
  <button type="submit">送信</button>
</form>

<p>@ViewData["Message"]</p>

サーバー側である HomeController.cs では送信されたテキストを加工して Visual Studio のトレースに出力します。 同じテキストを ViewData にも設定してクライアントに表示できるようにします。

HomeController.cs

public class HomeController : Controller
{
  // 省略

  [HttpPost]
  public IActionResult Index(string text)
  {
    System.Diagnostics.Trace.WriteLine($"「{text}」が入力されました。");
    ViewData["Message"] = $"「{text}」が入力されました。";
    return View();
  }

  // 省略
}

作成した機能を普通の手順で実行してみます。デバッグ実行を行い画面が表示されたらテキストを入力して送信ボタンをクリックします。

想定した通り加工されたテキストが画面に表示されます。

トレースにも出力しているので、Visual Studio の出力ウィンドウにもテキストが表示されています。 実際には入力データをデータベースに登録するなどの処理を行いますが、ここではコードの単純化のために省いています。

では、プログラムをデバッグ実行した状態で対象サイト外からアクセスしてみます。 今回は実装の簡略化のために Visual Studio Code というツールを使用して POST 送信してみます。 POST 送信できるのであれば、他のツールを使用したり攻撃用の別サイトプログラムを構築しても問題ありません。

Visual Studio Code には REST Client という拡張機能をインストールすると .http ファイルを開くだけで簡単に REST API の動作確認を行うことができる機能を使用することができます。

テキストファイルで以下のような .http ファイルを作成し、ファイルを Visual Studio Code で開いたら Send Request をクリックすることで GET や POST の送信を行うことができます。

以下の POST データを作成すれば画面でテキストを入力して送信ボタンを押したときとほぼ同等のデータが送信されます。 (localhost のポート番号は実行環境に合わせてください)

RestTest.http

### Form で POST 送信
POST https://localhost:44372/home/index HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="text"

なんらかのテキスト
------WebKitFormBoundary7MA4YWxkTrZu0gW--

送信してみるとサーバー側がテキストを受け取って処理していることが分かります。

Visual Studio Code 側は結果を取得することができるのですが、 結果コードをよく見てみると ViewData に設定されていた値が表示されていることも分かると思います。 (Unicode で表示されていますが、Web ブラウザで見れば正しくテキストは表示されます)

クロスサイトリクエストフォージェリ脆弱性に対応する

冒頭でも述べた通り、ASP.NET Core にはクロスサイトリクエストフォージェリに対する対策がフレームワークに組み込まれています。 一般的な対策としては POST などの必要がある画面では、事前にクライアントにユニークなトークンを発行しておき、 そのトークンをサーバーに投げない限りは処理を受け付けないとする方法です。

もちろん外部サイトから直接 URL にアクセスした場合はトークンが不明なので処理を受け付けません。

まず、クライアント(HTML) にトークンを発行する方法についてですが、実はこの処理はフレームワーク側で自動で 行ってくれます。form タグに POST メソッドなどを指定しておけば勝手にトークンパラメータを HTML に組み込んでくれます。

下図の HTML でいうと input name="__RequestVerificationToken" type="hidden" の箇所になります。 これが送信ボタン押下時に一緒にサーバーに送信されます。 ちなみに GET メソッドでは付加されません。

サーバー側については Startup.cs の services.AddControllersWithViews メソッドの options.Filters に AutoValidateAntiforgeryTokenAttribute を設定しておくだけで、全アクション (正確には POST, PUT, PATCH, DELETE の HTTP メソッドのみ) にこのトークンチェックが自動で追加されます。

もし、全アクションではなく、特定のアクションのみにこの制約を付加したい場合はコントローラーごと、アクションごとに 設定することも可能です。

逆に大部分のアクションには制約を入れたいが、特定のアクションについては除外したい場合は IgnoreAntiforgeryToken 属性をコントローラー、アクションに設定すれば OK です。

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllersWithViews(options =>
  {
    // 全ての「POST, PUT, PATCH, DELETE」アクションに自動で ValidateAntiForgeryToken を付与。
    // 個別に除外したい場合は「IgnoreAntiforgeryToken」属性を指定すること
    // API では HTML 側にトークンを発行できないのでコントローラーに「IgnoreAntiforgeryToken」を指定する必要がある。
    options.Filters.Add(new Microsoft.AspNetCore.Mvc.AutoValidateAntiforgeryTokenAttribute());
  });
}

では動作を確認してみましょう。まずは普通に画面から送信処理を行います。

期待通りに画面に結果が反映されました。

トレースにも出力されています。

では、外部からアクセスしてみます。

アクセスはしていますが Bad Request が返されました。対策していないときとは別な結果になっています。 トレースにも出力されていないのでガードされていることが分かります。

まとめ

今回はクロスサイトリクエストフォージェリに対する対策を実施してみました。 フレームワークにすでに組み込まれているので実装はとても簡単でした。

Web サイトの構築では CSRF 以外にも非常に多くの脆弱性対策が必要になるので どんなものがあるか調べてみたり OWASP などのツールを使って自分のサイトの脆弱性を調べてみるもいいでしょう。