Laravel 4のCSRF脆弱性は何が問題だったのか?

PHPでのある意味「典型的な脆弱性」だったので記事を書くことにしました。

Laravel 4のCSRF脆弱性とは?

Laravel 4.2.10以前にCSRF保護が無効になる脆弱性が報告されました。

この脆弱性は、Laravel標準のCSRF保護(csrfフィルタ)を簡単に無効化することができるものです。

既存サイトでは、今すぐ、以下の修正パッチを摘要する必要があります。Laravelのアップデートでは修正されません。

From ba0cf2a1c9280e99d39aad5d4d686d554941eea1 Mon Sep 17 00:00:00 2001
From: Taylor Otwell <taylorotwell@gmail.com>
Date: Sun, 9 Nov 2014 16:29:56 -0600
Subject: [PATCH] Check type of token as well.

---
 app/filters.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/filters.php b/app/filters.php
index fd0b4bc..97a9468 100644
--- a/app/filters.php
+++ b/app/filters.php
@@ -83,7 +83,7 @@

 Route::filter('csrf', function()
 {
-   if (Session::token() != Input::get('_token'))
+   if (Session::token() !== Input::get('_token'))
    {
        throw new Illuminate\Session\TokenMismatchException;
    }

https://github.com/laravel/laravel/commit/ba0cf2a1c9280e99d39aad5d4d686d554941eea1.patch

新規にインストールしたLaravel 4.2.11以降では修正されています。

何が問題だったのか?

修正パッチを見れば、まっとうなPHPユーザの方はすぐにわかると思いますが、比較が!=でされていました。

このため、比較する値によっては自動型変換(キャスト)が起こり、意図せずこのチェックをすり抜けることが可能になっていた、ということです。

この点をよくご存じないという方は、以下などを参照して下さい。

でもGETやPOSTパラメータは文字列なのでは?

はい、PHPに詳しい方は、Input::get('_token')の返り値は文字列なのではないか?と疑問に思われたのではないかと思います。

Input::get()というメソッド名からは、$_GETへアクセスするメソッドかなと思うPHPユーザもいると思いますが、このメソッドはそんなに単純なものではありませんでした。

具体的な実装は、以下のようになっていました。

たぶん、ほとんどのLaravelユーザが、このInput::get()が文字列以外も返す可能性があることを知らなかったために、この脆弱性が長い間放置された原因なのではないかと想像します。

フィルタを定義するapp/filters.phpというファイルにあるコードが、ユーザの目に触れなかったということは考えにくいですから。

まとめ

  • PHPでの文字列の比較では==!=は使ってはいけません。

参考

Date: 2014/11/11

Tags: php, csrf, security, laravel