RAKUS Developers Blog

株式会社ラクスのエンジニアブログ

意図しない処理が実行されるCSRFとは?概要と対策

はじめに

 こんにちは、mickey-STRANGEです。昨年に新卒でラクスに入社しました2年目です。

 新卒に毛が生えた程度の新米エンジニアですが、今回はその数少ない毛の中から学生時代に意識したことのなかったものという観点で脆弱性のお話を選び、記事にしました。新しくWeb開発企業に入社した新卒の方の学習の手助けになればと思います。

目次

脆弱性とは?

 脆弱性とは、セキュリティの面からみたシステムの欠陥のことです。 脆弱性をついて攻撃されてしまうと、ユーザには本来許されていない操作ができてしまったり、サーバにウィルスを仕込まれてしまったりという被害が出ます。

 この記事では脆弱性の中でも、私が実際に理解に時間のかかった

という脆弱性の概要と対策をご紹介したいと思います。

CSRF(クロスサイトリクエストフォージェリ)

概要

 サービスの利用者に意図しないHTTPリクエストを送信させ、利用者の意図しない処理をサービスに実行させる攻撃です。と、1文で書いてはみるものの、これだけで理解は出来ないと思います(私が勉強したときは理解出来ませんでした)ので、shop.example.comという架空の通販サイトで簡単な例を挙げてみましょう。

 shop.example.comではユーザはIDとパスワードでログインします。利用ユーザがパスワードを変更しようとしたとき(ユーザが新しいパスワードを入力して確定ボタンを押したとき)に送信されるリクエストが下のようなものだったとします。

URL
 http://shop.example.com/password/change
パラメータ
 new_pass:【新しいパスワード】
 new_pass_conf:【新しいパスワード(確認入力用)】

 このリクエストと同じものを作成して送信させる悪意のあるWebページを攻撃者が用意します。 偽装リクエストを送信さえできればよいのでWebページの用意は難しいことではありません。具体的なコードは示しませんが、javascriptを使って数行で実現出来ます。GETでよければ他の手段でもっと単純に、コードとしては1行でも十分に実現できてしまいます。(確定処理でGETを使用すること自体が論外ですが。)

 さて、shop.example.comのユーザAさんが、攻撃者の用意した悪意のあるWebページを開いてしまったとしましょう。

 するとAさんのブラウザから上記リクエストと同じものが勝手に送信されてしまいます。正規にパスワード変更画面で確定ボタンを押したときのリクエストと違うところがあるとすれば、パラメータのパスワード部分は攻撃者しか知らないものである、ということだけです。 このときAさんのブラウザに「shop.example.comにログインしている状態のセッション情報」が残っていた場合、その情報もブラウザが同時に送信してしまいます。 そのリクエストを受け取ったサーバ側のプログラムはAさんが操作を再開して、パスワードの変更確定ボタンを押したと誤認し、パスワード変更の処理を行ってしまいます。パスワード変更の処理が完了するとAさんはもうログイン出来なくなってしまいます。

 CSRFログイン済みのユーザに、意図しない操作を強制的に実行させてしまう攻撃であるといえます。

 今回の例ではパスワード変更ですが、これが購入や決済の確定処理で起きてしまうと取り返しがつかないということは簡単に想像出来ると思います。ではCSRFがどのような脆弱性か分かったところで、CSRF攻撃を防ぐためにどのような仕組みを入れればよいかを考えてみようと思います。

対策

 ではCSRFの対策を考えてみましょう。 上記の通り、CSRFにおいてサーバはリクエスト通りに1つの機能を正しく完了しているという特徴があります。つまり対策として考えられるのは受け取ったリクエストに対して処理を実行するかしないかを確認することになります。

確定処理の前に認証を行う

 一番分かりやすい方法はユーザに確認してもらうことです。重要な処理の前にはユーザにもう一度認証を行う、つまりログインIDとパスワードの入力をしてもらうことになります。今回のパスワード変更の例ですと、以下のようになります。(数字がユーザ操作、→がサーバ処理です。)

1.新しいパスワードを入力して確定ボタン
2.認証画面でログインIDとパスワードを入力
 →変更処理を行う

 この順番で操作してもらうようになっていれば、今回の例のように1番のリクエストを偽装されても処理を行ってしまうことはありません。

 しかし、この方法で対策するとユーザの操作が増えてしまいます。大事な処理の前だけとしても、ユーザの操作量が増えてしまったり、直感的に進めない画面が表示されると「このサイトは使いにくい、面倒だ」と感じてしまうかもしれません。ユーザに負担をお願いしたくない、ということで次にプログラム側だけで出来る対策を考えてみます。

リファラを確認する

 ユーザ操作を増やさずに出来るCSRF対策は「送られてきたリクエストが正しいものか確認する」ことです。ここで正しいリクエストとは、サイト内にある確定ボタンを押すことで送信されたリクエストということになります。それを確認するために、リファラというHTTPヘッダの1つを利用します。

 リファラとは、リクエスト元のページのURLを示すHTTPヘッダです。処理の前にリファラの値がhttp://shop.example.comから始まっているかを確認すれば、偽装リクエストかどうか判定することが可能です。

1.新しいパスワードを入力して確定ボタン
 →リファラのチェックを行う
 →変更処理を行う

 しかし、この方法ではまだ完璧な対策ではありません。リファラはその特性上、プライバシー面に問題を抱えており、ブラウザのでリファラを送信しない設定が可能です。また、前述のとおり、リファラはHTTPヘッダの1つです。改竄されてしまっては元も子もありません。

ワンタイムトークンを利用する

 ワンタイムトークンとは、リクエストが正しいものか判断するための文字列をリクエストの中に仕込む手法です。

 今回のパスワード変更の例ですと、以下のような流れになります。

0.パスワード変更画面を開く
 →乱数文字列(トークン)を生成
1.新しいパスワードを入力して確定ボタン(トークンを付与したリクエストを送信)
 →サーバ側で保持しておいたトークンとリクエストで送信されたトークンの比較を行う
 →変更処理を行う

 パスワード変更画面を開く際にトークン(乱数文字列)を作成し、セッションなどのサーバ側の領域で一時保存しておきます。また、確定のリクエストの時にそのトークンをパラメータに追加して送信させ、サーバ側でトークンの照合を行います。サーバ側で保存していたトークンと一致すれば処理を行う、一致しなければ不正なリクエストとして処理を行わない、といった判断が可能となります。

 偽装リクエストがもしトークンを勝手に付与したリクエストを送信してもサーバ側で同じトークンを保持していないので判別できますし、リファラとは違いヘッダではなくパラメータなのでユーザ依存で失敗したりすることがありません。今回紹介した中ではこの手法が最も適切だといえるでしょう。

おわりに

 CSRFの概要と対策についてご紹介いたしました。いかがでしたでしょうか。 XSS(クロスサイトスクリプティング)と名前が似ていることで勘違いしやすい脆弱性で、自分が理解しづらかったCSRFについて記事にしてみました。

 Web開発に関する勉強を始めたばかりの方の手助けになれば幸いです。

Copyright © RAKUS Co., Ltd. All rights reserved.