C#のCSRF(クロスサイトリクエストフォージェリ)対策を徹底解説!セキュリティ初心者のための完全防衛ガイド
生徒
「C#を使ってWebサイトを作ってみたいのですが、セキュリティ対策も自分でやらないといけないと聞きました。特にシーエスアールエフというものが危ないと聞いたのですが、それは一体何ですか?」
先生
「それはとても大切な視点ですね。シーエスアールエフは日本語でクロスサイトリクエストフォージェリと呼びます。これは、悪意を持った人が作った偽の罠サイトを経由して、ユーザーが気づかないうちに、本物のWebサイトで勝手に重要な操作をさせられてしまうという、とても恐ろしいサイバー攻撃の仕組みのことです。」
生徒
「自分が知らない間に勝手に操作されるなんて怖いです。パソコンを触ったばかりの私でも、C#のプログラムでそれを防ぐことはできるのでしょうか?」
先生
「安心してください。C#の開発環境であるドットネットには、この攻撃を完全に防ぐための強力な仕組みが最初から用意されています。今回は、プログラミングが初めての方でも絶対に理解できるように、仕組みから具体的な対策コードまで、基礎知識をじっくりと丁寧に解説していきますね。」
生徒
「ありがとうございます。悪意のある攻撃からWebサイトを守る方法を、ぜひ教えてください!」
先生
「それでは、初心者の方でも分かりやすいように、身近な例えを交えながら基本的な対策方法を見ていきましょう!」
1. クロスサイトリクエストフォージェリとは?
クロスサイトリクエストフォージェリとは、Webサイトのセキュリティに存在する隙を狙った攻撃方法のことです。英語の頭文字を取ってCSRFとも呼ばれます。これの仕組みをパソコンの操作に慣れていない方に向けて、現実の世界で例えて分かりやすく説明します。
あなたが、ある銀行の窓口に手続きをしに行ったとします。窓口の担当者は、あなたの顔や身分証明書を確認して、本人であることを認めました。これをプログラミングの世界では認証と言います。手続きが終わってあなたがベンチで一休みしているとき、見知らぬ怪しい人物がやってきて、あなたに内緒で「この人の口座から別の人にお金を振り込んでください」という偽の手続き書類を窓口に提出してしまいました。窓口の担当者は、あなたがまだ店内にいるのを見て、あなたがその書類を出したものだと勘違いして、そのままお金を振り込んでしまいます。これがまさに、クロスサイトリクエストフォージェリの被害に遭った状態です。
Webサイトの世界では、あなたが一度ログインした状態のまま、別の悪意ある罠サイトを開いてしまったときにこの被害が発生します。罠サイトに仕込まれた自動送信ボタンなどが勝手に作動し、あなたがログイン中の本物のWebサイトに対して、パスワードの変更や商品の購入といった命令を勝手に送信してしまうのです。Webサイトのサーバー側は、あなたからの正式な命令だと勘違いして、その処理を実行してしまいます。このように、ユーザーの意思を無視して、勝手にリクエスト(命令)を偽造(フォージェリ)して送信させることが、この名前の由来となっています。
2. クッキーとセッションの基本的な仕組み
この攻撃を理解するために、まずはWebサイトがどのようにしてログイン状態を覚えているのかという、基本知識を学びましょう。通常、Webブラウザ(インターネットを見るためのソフト)とサーバー(Webサイトのデータを保管しているコンピューター)の間では、一回一回の通信が完全に独立しています。つまり、ページを移動するたびに、サーバーはあなたが誰なのかを忘れてしまいます。
これでは不便なので、使われているのがクッキー(Cookie)という仕組みです。クッキーとは、サーバーからあなたのパソコンに配られる、小さな会員証のようなデータカードです。あなたが一度ユーザー名とパスワードを入力してログインに成功すると、サーバーは「あなたは会員番号100番のユーザーですね」という情報が書かれたクッキーをパソコンに保存します。それ以降、あなたがそのサイト内で別のページを見るたびに、ブラウザが自動的にそのクッキーをサーバーに提示してくれるため、何度もログインし直す必要がなくなります。この一連のログイン状態の維持をセッション管理と呼びます。
しかし、ここに落とし穴があります。ブラウザというソフトは、特定のWebサイト宛てのクッキーを持っている場合、それが本物のページからの通信であっても、悪意ある罠サイトから呼び出された通信であっても、区別をせずに自動的にクッキーを添付して送信してしまうという性質を持っています。この自動的に送られてしまう性質を悪用するのが、今回の攻撃の根本的な原因なのです。
3. 攻撃を防ぐための合言葉であるアンチフォージェリトークン
では、C#ではどのようにしてこの勝手な通信を防ぐのでしょうか。最も代表的で強力な対策が、アンチフォージェリトークンという仕組みの導入です。これは日本語に直すと「偽造防止用の合言葉」という意味になります。
仕組みはとてもシンプルです。ユーザーが本物のWebサイトの画面を開いた瞬間に、C#のプログラムは予測が不可能な、長いランダムな文字列(合言葉)を2つ発行します。1つ目の合言葉は、先ほど説明したクッキーの中に隠して保存します。そして2つ目の合言葉は、画面上にある入力フォーム(送信ボタンなどがある場所)の中に、ユーザーの目には見えない隠し項目として埋め込みます。この目に見えない入力項目のことを、専門用語でhtmlのinputタグのhidden属性と言います。
ユーザーが画面にある「決定」や「送信」のボタンを押したとき、パソコンからは「クッキーの中の合言葉」と「画面の隠し項目にある合言葉」の2つが同時にサーバーへ送信されます。サーバー側のC#プログラムは、届いた2つの合言葉が完全に一致しているかどうかを厳しくチェックします。もし、罠サイトから勝手に送りつけられた通信の場合、クッキーはブラウザの仕組みで自動送信されますが、罠サイトの制作者は本物の画面の中に埋め込まれた隠し合言葉を知ることができないため、2つの合言葉が揃いません。サーバーは合言葉が一致しないことを検知して、その命令を偽物と判断し、処理を途中で安全に遮断します。これが、C#で行う最も基本的な防衛策です。
4. HTML画面に合言葉を埋め込む実装方法
ここからは、実際にC#を使ってWebアプリケーションを作る際の、具体的なプログラムコードの書き方を見ていきましょう。現代のC#開発では、主にASP.NET Core(エーエスピーネットコア)という便利な仕組みを使ってWebサイトを作ります。この仕組みには、最初から合言葉を自動で発行する機能が組み込まれています。
まずは、ユーザーが文字を入力したりボタンを押したりする、HTML(エイチティーエムエル)と呼ばれる画面側のプログラムの書き方です。下記のように、フォームという入力領域を定義するタグの中に、C#の特別な命令を1行記述するだけで、安全な合言葉を画面に埋め込むことができます。
<form method="post" action="/Account/UpdatePassword">
@Html.AntiForgeryToken()
<div class="mb-3">
<label>新しいパスワードを入力してください</label>
<input type="password" name="NewPassword" class="form-control">
</div>
<button type="submit" class="btn btn-primary">変更を保存する</button>
</form>
このコードの中にある、アットマークから始まる@Html.AntiForgeryToken()という部分が、C#独自の非常に重要な命令です。この1行を書いておくだけで、システムが実行されたときに、ブラウザからは見えない秘密の文字列が自動的に生成され、画面の裏側にセットされます。プログラミング未経験の方でも、この呪文をフォームの中に必ず書くということだけ覚えておけば、安全性を一気に高めることができます。
5. サーバー側で合言葉を厳しく検証するC#コード
画面側に合言葉を埋め込んだら、次はそれを受け取るサーバー(C#側)のプログラムにも、送られてきた合言葉をチェックするための設定を行う必要があります。チェックを強制するためには、処理を行うC#の関数(メソッド)の真上に、特別な属性と呼ばれる目印を付けます。
下記のC#プログラムは、ユーザーから送られてきた新しいパスワードを受け取って、処理を行う部分の書き方です。関数の上に、検証を行うための命令が記述されている点に注目してください。
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult UpdatePassword(string NewPassword)
{
if (string.IsNullOrEmpty(NewPassword))
{
return BadRequest("パスワードが正しく入力されていません。");
}
return Ok("パスワードの変更が安全に完了しました。");
}
このプログラムの3行目にある[ValidateAntiForgeryToken]という記述が、セキュリティの盾の役割を果たしています。この目印がついている関数は、画面から送られてきた合言葉と、クッキーの中の合言葉が完全に一致しない限り、中身の処理を絶対に実行しません。万が一、悪意のあるサイトからパスワード変更の命令が飛んできても、この盾が自動的にエラーを返して守ってくれます。これにより、プログラムの内部が不正に書き換えられる心配がなくなります。
6. サイト全体に自動でセキュリティの盾を適用する方法
先ほどの方法は、データを変更する関数の一つひとつに目印をつけていく書き方でした。しかし、作成するページが何十個、何百個と増えていくと、人間の作業ですから、どうしても書き忘れてしまうミスが発生します。セキュリティの世界では、たった一つの書き忘れが重大な事故に繋がります。
そこで、C#のASP.NET Coreでは、システム全体に対して一括で「データを受け取るすべての処理に合言葉のチェックを義務付ける」という、強力な初期設定を行うことができます。Webアプリの起動時に呼び出される設定ファイルの中に、下記のプログラムを記述します。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
var app = builder.Build();
app.Run();
このプログラムの5行目にあるAutoValidateAntiforgeryTokenAttributeという設定を追加することで、個々の関数にわざわざ目印を書かなくても、データを変更する可能性のあるすべての通信(POST通信など)に対して、自動的に合言葉の検証が行われるようになります。この設定をしておけば、新しくページを追加したときにも対策の漏れがなくなるため、実務の開発現場でも非常によく使われている推奨される書き方です。
7. 非同期通信で合言葉を送信する高度な対策コード
最近の洗練されたWebサイトでは、ボタンを押したときに画面全体を白くして書き換えるのではなく、画面の一部分だけを滑らかに更新する手法がよく使われます。これを非同期通信、またはAjax(エイジャックス)通信やJavaScript(ジャバスクリプト)を用いた通信と呼びます。
通常のフォーム送信ではないため、先ほどのHTMLの書き方だけでは合言葉がサーバーに自動で送られません。そのため、通信を行う際のヘッダーと呼ばれる見えないデータ領域に、手動で合言葉を載せてあげる必要があります。下記のC#プログラムは、非同期通信を受け取る側で、ヘッダーから合言葉を取り出して正当性を手動でチェックする実装例です。
using Microsoft.AspNetCore.Antiforgery;
public class SecurityController : Controller
{
private readonly IAntiforgery _antiforgery;
public SecurityController(IAntiforgery antiforgery)
{
_antiforgery = antiforgery;
}
[HttpPost]
public async Task<IActionResult> ProcessAjaxData()
{
try
{
await _antiforgery.ValidateRequestAsync(HttpContext);
return Json(new { success = true, message = "認証に成功し、データを処理しました。" });
}
catch (AntiforgeryValidationException)
{
return BadRequest("不正なアクセスと判定されました。");
}
}
}
このプログラムでは、17行目で_antiforgery.ValidateRequestAsyncというC#の機能を直接呼び出しています。これにより、パソコンから送られてきた特殊なデータの中に合言葉が含まれているかを、細かく調べることができます。もし合言葉が盗まれていたり、含まれていなかったりした場合は、20行目のキャッチという場所でエラーを捕まえ、22行目のように「不正なアクセスです」という結果を出力して、安全に処理を終了させます。
上記のプログラムを実行した際の出力結果は、以下のようになります。合言葉が正しくない状態でアクセスすると、この結果が表示され、中のデータは守られます。
不正なアクセスと判定されました。
もし、正常に正しい画面から非同期通信が行われた場合は、以下のような結果が画面に返ってきます。このように、初心者向けの簡単なシステムから、プロが作る高度な非同期システムまで、C#は柔軟に対応できる仕組みを持っています。
認証に成功し、データを処理しました。
8. 安全性を高めるクッキーの追加設定項目
これまで紹介した合言葉の仕組みに加えて、さらにセキュリティをガチガチに強固にするための重要な設定があります。それは、合言葉や会員証を保存しているクッキーそのものに対して、「他のサイトからは絶対に見えないようにする」「暗号化された安全な通信のときだけ送信する」という制限をかける方法です。
これを実現するために、C#の初期設定の中にクッキーの動きを細かく制御するコードを追加します。プログラミングをやったことがない人にとっては少し難しく見えるかもしれませんが、安全なWebサイトを作るためには必須の知識です。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAntiforgery(options =>
{
options.Cookie.Name = "MySecureTokenCookie";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
});
var app = builder.Build();
app.Run();
このプログラムの中で注目してほしいキーワードが3つあります。まず6行目のHttpOnly = trueは、悪意のあるプログラムからクッキーの中身を盗み見られないようにする鍵です。次に7行目のSecurePolicy = Alwaysは、インターネット上の通信が暗号化されている(HTTPSという安全な状態の)ときだけ、クッキーを送信するというルールです。そして8行目のSameSite = SameSiteMode.Strictが最も強力で、「他の外部サイトから出発した通信には、絶対にこのクッキーを添付させない」という、最高レベルの制限をかける命令です。これらの設定をC#のシステムに組み込んでおくことで、そもそも罠サイトがあなたのクッキーを悪用すること自体を、ブラウザの根本的なレベルで完全にブロックできるようになります。