ajaxでAntiforgeryする処理(ASP.NET CORE)

C#

公式でこんな感じで書くようにと言われてますが、ぱっと見では理解が追い付かなかったので解釈を。

public class ApiController : ControllerBase
{
    [HttpPost]
    [ValidateAntiForgeryToken]
    public string GetText()
    {
        return "HOGE";
    }
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

<div class="hidden">
    <input type="hidden" id="RequestVerificationToken"
           name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">
</div>
var apiButton = document.getElementById('apiButton');

apiButton.onclick = () => {
    const token = document.getElementById('RequestVerificationToken').value;

    const option = {
        method: "post",
        credentials: "same-origin",
        headers: {
            Accept: 'application/text',
            'Content-Type': 'application/text',
            "RequestVerificationToken": token
        }
    };

    fetch('api/GetText', option)
        .then(res => res.json()).then(console.log);
};
Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core
Discoverhowtopreventattacksagainstwebappswhereamaliciouswebsitecaninfluencetheinteractionbetweenaclientbrowserandtheapp.

IAntiforgery.GetAndStoreTokens(Context)
のメソッドの中身が理解できずに苦労しました。
「GetAndStore」と書いてあるように、CookieTokenが生成され自動的にCookieに保存されます。
それとは別にHTTPリクエストヘッダーに付与するためのRequestTokenも生成されます。
Tokensですから複数(2種類)生成されます。
なんで2種類?とか、勝手にCookieが生成されるのはなぜ?
Headerにしていする「RequestVerificationToken」はどこから来たの?と無駄に悩みました。

根本的にCSRFの対策方法を理解する必要があります。
ブラウザに同じキーを2つ渡し、サーバにリクエストする際に2つのキーを一緒に送ってもらいます。
1つはHttpOnly=trueのCookieTokenとしてjsから操作できない状態で渡し、もう1つはhidden inputなどでhtmlに埋め込んだり、Cookieにするなりでjsが操作できる状態で渡します。
サーバ側は渡された2つのキーが一致しているかを見て、偽証がないかを確認します。

この方法はDouble Submit Cookieと呼ばれ、サーバ側で状態を持たなくてよいため流行ったようです。
一方で、CookieTokenが読み取られもせず、書き換えられもしないという前提に立った防衛方法となっているため、セキュリティ上完全とは言えません。

ASP.NETでどうやってこの辺を解決しているのかはわかりませんが、
実際に挙動を見る限り、CookieTokenとRequestTokenは別の値が格納されています。

実例を挙げると以下のような感じで同じようなPrefixが付いた別のコードが生成されます。

CookieToken:CfDJ8EmGBURTov5HpC6F5DuHjHKpqDs4Mpr_g95HuOLONpDCZShWCX1...
RequestToken:CfDJ8EmGBURTov5HpC6F5DuHjHLvSjyJORSzL-X33jD6y0qefPWKyv...

理解しきれませんでしたが、ソースを斜め読みした感じでは
ランダム生成のセキュリティキーを作成。
CookieTokenにはセキュリティキーのみ付与し暗号化。
これにUser情報などを付与し、暗号化してRequestTokenを作成。
という手順っぽいです。

Tokenの評価時にはそれぞれのTokenの暗号化を解除し、Tokenオブジェクトに変換。
それぞれのセキュリティキーの一致をみているようです。
加えて、RequestTokenの方にはUser情報等が乗っているので、それらもチェックされています。
つまりセキュリティキーの一致のみならず、ユーザ認証情報とも照合しているわけです。100%安全かどうかはわかりませんが、単純なDouble Submit Cookieでは攻撃者は強引な手法でCookieを差し替えればよかったのが、User情報ともマッチするキーを生成する必要が出てきます。さらに暗号化のおまけつきで。

逆にRequestTokenの方が情報が多い上にjsで触れてしまうため、CookieTokenにRequestTokenと同じものを載せても認証通るんじゃないか?と思ったのですが実際にやると通りませんでした。同じロジックで暗号化されているのであれば同じTokenオブジェクトに変換されて、セキュリティキーも同じになるはず。どうやってバリデーションしてるんだろう?

https://github.com/dotnet/aspnetcore/tree/v3.1.4/src/Antiforgery/src/Internal

このサイトも詳しくてとても参考になりました。

ASP.NET Core CSRF defence with Antiforgery | DotNetCurry
ASP.NetCorecontainsanAntiforgerypackagethatcanbeusedtosecureyourapplicationagainstCSRF.ThisarticlewilldemonstratehowtouseAntiforgeryinyourASP.NETCoreapplication...

コメント