2020/10/27
セキュリティ CSRFCSRF(クロスサイトリクエストフォージェリ)の意味と対策方法
Webサービスを開発する上で知っておきたいCSRF(クロスサイトリクエストフォージェリ)について解説します。
CSRFの対策をしないと以下のような問題があります。
- Webサービスにセキュリティ面での脆弱性が生まれてしまう
- 悪意のあるユーザーによって攻撃されてしまう
- 結果としてWebサービスを利用してくれたユーザーに迷惑をかけてしまう
CSRFとは
超ざっくり言うと、「悪意のある人(加害者)が作ったサイトにアクセスした人(被害者)のパソコンから、勝手に別のWebサイトに対してHTTPリクエストが送られてしまい、そのWebサイト上で被害者が本来意図していないアクションを強制的に取らされる」というものです。
「被害者が本来意図していないアクション」の例には以下のようなものがあります。
- 被害者のアカウントでなりすまし投稿が行われる(勝手に殺害予告の投稿がされる等)
- 勝手にパスワードが変更される
- 勝手にアカウントを削除される
- 勝手に商品が購入される
- 非公開に設定していた情報が勝手に公開される
具体例
具体例がないとよくわからないと思うので、具体例で説明します。
以下のように2つのウェブサイトがあったとします。
- サイトA: 掲示板サイト
- サイトB: 悪者が用意したサイト
サイトAはどこにでもあるような一般的な掲示板サイトです。(ただし、CSRF対策がされていません。)「コメント投稿画面」にはユーザーがコメントを入力できる投稿フォームがあります。このフォームが送信されたとき、HTTPリクエストが送られます(送信先URL: サイトA.com/new_message/
, パラメーター: message="ユーザーがフォームに入力したメッセージ"
)
サイトAは受け取ったHTTPリクエストをサーバー側で処理し、message
パラメーターの文言を保存&掲示板に表示します。
サイトBは、悪意のある人間が作ったサイトです。このサイトには、「ボタン」が表示されています。このボタンは、表面上はただ次の画面へ遷移するだけのボタンです。見かけ上はどこにでもある何の変哲もないボタンです。しかし、実際はこのボタンを押すと、サイトAのURLサイトA.com/new_message/
に対してHTTPリクエストが送信される設定にされています。しかも message=明日県庁を爆破する
というパラメータで。
Jobsという人がサイトAにログインした後、同じブラウザでサイトBも閲覧したとします。
そして、画面遷移するために「ボタン」を押すと、JobsのパソコンからサイトAに対して「明日県庁を爆破する」というメッセージのHTTPリクエストが送られます。サイトAはCSRF対策がされていないので、そのメッセージを保存&表示します。
Jobsは、訪れたサイトで画面遷移ボタンを押しただけなのに、爆破予告犯にでっち上げられてしまうわけです。
サイトBからサイトAに対してHTTPリクエストが送られるように、複数のサイトにまたがって偽造されたリクエストが送られるのでクロスサイトリクエストフォージェリと呼びます。
対策は?
上記の例で問題があるのは、サイトAが受け取ったHTTPリクエストを何の検証もしないまま処理し、messageパラメーターの文言を保存&掲示板に表示する
の部分です。(当然一番の問題点は悪者がサイトBを作ってる事ですが、悪者は必ず存在する前提で、サイトAの製作者が対応をしておく必要がありました。)
サイトAはHTTPリクエストを受け取った際に、そのリクエスト元にかかわらず処理(メッセージの保存)をしてしまいます。たとえ悪意のある外部サイトからのリクエストであったとしてもです。これは、CSRF対策ができていないということになります。
対策方法としては、悪意のあるユーザーからのHTTPリクエストを排除して、正常な(サイトAのコメント投稿画面から受け取った)投稿だけを保存するようにすれば良いわけです。つまり、外部のサイトからは、HTTPリクエストを受け取れないようにします。
CSRFの対策はいくつかありますが、その中でもトークンを利用する対策方法があります。
ユーザーが投稿画面にアクセスした時にランダムな(他者からは推測しづらい)文字列をトークンとして発行し、「コメント投稿」のフォームにパラメーターとして追加します。
フォームが送信されて、HTTPリクエストを受け取ったサーバーは、リクエストパラメーターに正しいトークンが含まれているかを検証するように実装します。 検証の結果、トークンが含まれていることが確認できれば、そのリクエストは「コメント投稿画面」からのリクエストであることを確認できるので、正常にメッセージ保存の処理を行うようにします。
トークンの検証に失敗した場合は、外部からHTTPリクエストを受け取ったことを意味するので、保存処理を実行しません。