🔑

CSRFについてまとめ

2022/02/01に公開約2,200字

CSRFとは

しーさーふと読むことが多いようです。正規ユーザの権限を使って意図しないタイミングでサービスの機能を実行させる手法で。APIリクエストを変なタイミングでされる感じ。以下で説明するようにJavaScriptにはクロスドメインでの非同期通信の制限はあるが、リクエスト自体はAPIに届くので別途対策が必要。

CORSとは

こるすとよむことが多いようです。web開発をしていると一度はハマるやつ。これはJavaScriptのSame Origin Policyという仕様で、 「クロスドメインで非同期通信ができない」 という代物。これを避けるにはAccess-Control-Allow-OriginヘッダをAPI側からレスポンスに付与する必要がある。

ちなみにクロスドメインというのは同一生成元ポリシーを満たしていないことで、「スキーム」「ホスト」「ポート」のどれかが一致していないことである。

流れとしてはAPI側がレスポンスヘッダを付与するとクライアント側のブラウザが通信可否の判断をすることになっていて、Access-Controll-Allow-なんとかのヘッダーで違反するものがないかをみてくれる。基本的にAccess-Controll-Allow-Originが一番大事。Access-Control-Max-Ageがある場合はプリフライトの結果をキャッシュできる。

CSRFの対策方法

  • トークンを発行する
  • プリフライトリクエストの検証をする
    の2種類があります。

トークンの発行をする

サーバ側でトークンを発行して、トークン自体も検証する方法。有名なものとしてJWTの発行がある。ログイン時にJWTを渡して、これを持っている人がログイン済みとする。リクエスト時にJWTを渡すが、Authorizationヘッダを使うことが多い。
ちなみにシークレットクッキーだと防げません。JWT以外だとチャレンジトークンとかはありです。

プリフライトリクエストの検証をする

実際のリクエストの前にOPTIONメソッドを使って先にリクエストを行い、正規ルートだった場合のみ後続のリクエストを実行する方法。OriginヘッダがAccess-Control-Allow-Originと同じか、Hostが正規サービスかなどを検証する。CSRFだとリクエストが届いた瞬間攻撃完了…となってしまうので、先に確認のリクエストで防ごうねというロジックらしい。プリフライトは以下の条件を満たさないときに発行される。

  • HTTPメソッドがHEAD、GET、POSTのいずれか
  • HTTPリクエストヘッダが「単純リクエスト」を満たす
  • Contet-Typeがtext/plainかmultipart/form-dataかapplication/x-www-form-urlencoded

余談、SPAのときのJWTの扱いは?

localStrageではなくCookieに保存することが多いようです。ちなみにSPAでないときはセッションに保存します。(Cookieは容量に制限があるため)localStrageはXSSされると中身を保存できるらしい…が、まあ別に保存してもいいよねという派閥もある。

https://zenn.dev/marokanatani/articles/d0777a34641d22
実装は上記の記事がよさそうだったのでリンクをはっておきます。

参考記事

https://qiita.com/okamoai/items/044c03680766f0609d41
https://note.crohaco.net/2019/http-cors-preflight/
https://jpcertcc.github.io/OWASPdocuments/CheatSheets/Cross-SiteRequestForgeryPrevention.html

余談2 セキュリティに不安を感じたら

https://www.ipa.go.jp/security/vuln/websecurity.html
IPAのサイトにどんな攻撃があるかのパンフレットがあります。さらにチェックリストもあるので、フレームワークを使っている方もどこまで対策できているかセルフチェックをしてみるとよいかもしれません。

余談3 JWTってどう持つの?

普通にログイン時にJWTを発行して、リクエストの時に同時に送る形にするとセキュリティ上ずっとログインし続けることができません。ある程度でexpireさせる必要があります。これを防ぐにはログイン時にJWTを発行すると同時にリフレッシュトークンを発行します。
ログイン時にリフレッシュトークンもlocalStrageに保存して、JWTの有効期限がきたらリフレッシュトークンを取り出して照合、もしあっていれば再度JWTを発行します。こうするとリフレッシュトークンは普段のリクエストには使用されないのである程度安全である(盗聴されにくい)という前提の上、ある程度安全にログイン状態を継続することができます。

Discussion

ログインするとコメントできます