9.1 CSRF攻撃の予防
CSRFとは何か
CSRF(Cross-site request forgery)、我々の言語では:"跨站请求伪造(クロスサイトリクエストフォージェリ)"といいます。またone click attack/session ridingとも呼び、短縮して:CSRF/XSRFとなります。
CSRFではいったいなにができるのでしょうか?このように簡単に理解することができます:攻撃者はあなたのログイン情報を盗用することができ、あなたの身分であらゆるリクエストを送りつけることができます。例えばQQ等チャットソフトウェアを使ってリンク(URL短縮などで偽装したものもあり、ユーザは判別できません)を発信するなど、少しばかりのソーシャルエンジニアリングの罠を仕掛けるだけで攻撃者はWebアプリケーションのユーザに攻撃者が設定した操作を行わせることができます。たとえば、ユーザがインターネット銀行にログインし口座の残高を調べる場合、ログアウトしていない間にQQのフレンドから発信されたリンクをクリックするとします。すると、このユーザの銀行アカウントの資金は攻撃者が指定した口座に振り込まれてしまう可能性があります。
そのためCSRF攻撃を受けた時、エンドユーザのデータと操作コマンドは重大なセキュリティ問題となります。攻撃を受けたエンドユーザが管理者アカウントを持っていた場合、CSRF攻撃はWebアプリケーションプログラムの全体の危機となります。
CSRFの原理
下の図で簡単にCSRF攻撃の思想をご説明します
図9.1 CSRFの攻撃プロセス
上の図から、CSRF攻撃を一回成功させるには被害者が2つのステップを踏まなければならないことがわかります:
- 1.ログインがページAでの信任を受け、ローカルでCookieを生成する。
- 2.Aをログアウトしていない状態で、危険なページBにアクセスする。
ここまでで読者は次のように思われるかもしれません:"もし上の2つの条件のうち任意の一つを満足していなければ、CSRFの攻撃をうけることはない"。そうです。たしかにその通り、しかし以下の状況が発生しないことを保障することはできません:
- あるページにログインしたあと、もうひとつtab画面を開き別のページにアクセスしないとは保証できません。特に最近のブラウザはどれもtabをサポートしています。
- ブラウザを閉じたあと、ローカルのCookieがすぐに有効期限を迎えるとは限りません。前のセッションがすでに終了していることを保証できません。
- 上の図で示した攻撃ページは、その他のセキュリティホールを抱えた信用できるよくアクセスされるページであることがあります。
その為、ユーザにとってあるページにログインした後なんらかのリンクをクリックしてその他の操作を避けることはとても難しいのです。ですから、いつでもCSRFの被害者になる可能性があります。
CSRF攻撃の主な原因はWebの隠された身分検証メカニズムにあります。Webの身分検証メカニズムはあるリクエストがあるユーザのブラウザからやってきたものであることを保証することはできますが、このリクエストがユーザの承認によって送信されたものであることを保証できません。
どのようにしてCSRFを予防するか
上の紹介で、読者はこのような攻撃が非常に恐ろしいと思われたのではないでしょうか。恐怖を意識するのはいいことです。どのようにして似たようなセキュリティホールの出現を防止/改善し、以降の内容を引き続き読み進めていくことを促してくれます。
CSRFの防御はサーバとクライアントの両方から着手することができます。防御はサーバから手をつけるのが効果的です。現在一般的なCSRF防御はどれもサーバで行われます。
サーバでCSRF攻撃を予防する方法にはいくつかありますが、思想上はどれも大差ありません。主に以下の2つの方面から行われます:
- 1、GET,POSTとCookieを正しく使用する
- 2、GETでないリクエストで擬似乱数を追加する
前の章でRESTメソッドのWebアプリケーションをご紹介しました。一般的には普通のWebアプリケーションはどれもGET、POSTがメインで、もう一種類のリクエストはCookie方式です。我々は一般的に以下の方法でアプリケーションを設計します:
1、GETは閲覧、列挙、表示といったリソースを変更する必要のない場合に限ります
2、POSTは注文を行い、リソースに変更を加えたりといった状況に限ります
ではGo言語を使って例をご説明します。どのようにしてリソースのアクセス方法を制限するのでしょうか:
mux.Get("/user/:uid", getuser)
mux.Post("/user/:uid", modifyuser)
このように処理すると、修正にはPOSTのみに限定しているため、GETメソッドでリクエストした際レスポンスを拒絶します。そのため、上の図で示したGETメソッドのCSRF攻撃は防止されます。しかしこれですべて問題は解決されたのでしょうか?当然そうではありません。POSTも同じだからです。
そのため、第二ステップを実施する必要があります。GETでないメソッドのリクエストにおいてランダムな数を追加します。これはだいたい三種類の方法によって実行されます:
- 各ユーザにユニークなcookie tokenをひとつ生成します。すべてのフォームは同じ擬似乱数を含んでいます。この方法は最も簡単です。なぜなら攻撃者は第三者のCookieを(理論上は)取得することができず、そのため、フォームのデータを構成することができません。しかしユーザのCookieはページのXSSセキュリティホールによって容易に盗まれてしまいます。そのため、この方法はかならずXSSがない状況で安全だといえます。
- 各リクエストでCAPTCHAを使用します。この方法は完璧です。複数回CAPTCHAを入力する必要がありますので、ユーザビリティは非常に悪く実際の運用には適しません。
- 異なるフォームにそれぞれ異なる擬似乱数を含ませます。4.4節でご紹介した”どのようにしてフォームの複数送信を防止するか"で、この方法をご紹介しました。関連するコードを再掲します:
ランダムなtokenを生成します
h := md5.New()
io.WriteString(h, strconv.FormatInt(crutime, 10))
io.WriteString(h, "ganraomaxxxxxxxxx")
token := fmt.Sprintf("%x", h.Sum(nil))
t, _ := template.ParseFiles("login.gtpl")
t.Execute(w, token)
tokenを出力
<input type="hidden" name="token" value="{{.}}">
tokenを検証
r.ParseForm()
token := r.Form.Get("token")
if token != "" {
//tokenの合法性を検証
} else {
//tokenが存在しない場合はエラーを発生
}
このように基本的には安全なPOSTを実現しました。しかしもしtokenのアルゴリズムが暴かれてしまったらと思われるかもしれません。しかし理論上は破られることは基本的に不可能です。ある人が計算したところ、この文字列を無理に破るにはだいたい2の11乗の時間が必要です。
まとめ
クロスサイトリクエストフォージェリ、すなわちCSRFは非常に危険なWebセキュリティ問題です。Webセキュリティ界隈では"眠れる巨人"と呼ばれています。リスクレベルはこの"肩書き"を見ればお分かりでしょう。この節ではクロスサイトリクエストフォージェリの紹介にとどまらず、このようなセキュリティホールを生み出す原因の所在についても詳しくご説明しました。これでもって攻撃への防御を促し、読者に安全なWebアプリケーションを書いていただけますよう望んでいます。
links
- 目次
- 前へ: セキュリティと暗号化
- 次へ: 入力フィルタリングの確保