なぜPHPアプリにセキュリティホールが多いのか?

第43回 PHP 5.3のcrypt関数の問題

この記事を読むのに必要な時間:およそ 8 分

PHP 5.3.7のcrypt関数に重大なセキュリティ上の問題が発見されました。この問題は大きく報道されているのでご存知の方も多いと思います。同時にPHP本体のセキュリティ状態についても不安に思った方も多いと思います。

PHP 5.3.7のcrypt関数のバグは,攻撃が成功した場合のダメージは非常に大きく,攻撃が可能な場合は必ず成功します。しかし,攻撃経路があるシステムは限定的でしょう。

crypt関数のバグ

最近のPHPで行われたcrypt関数のバグ修正は3つあります。

PHP 5.3.7で修正したバグ ─ その1

PHP 5.3.7ではcrypt_blowfishを利用した場合,8ビット文字(マルチバイト文字エンコーディングなど)を利用した場合に脆弱になってしまう問題を修正しました。

この脆弱性は2011/6/20にレポートされており,crypt_blowfishのコードを含む製品に影響がありました。PHPの場合,PHP 5.3.0からcrypt_blowfishのコードをバンドルしており,5.3.6未満に影響がありました。

脆弱性があるcrypt_blowfishは8番目のビットがセットされた一文字に続く1から3文字を無視していました。問題の原因は符号あり・符合なし文字列の取り扱いでした。レポートによると調査したパスワードのうち3/16は8番目のビットがセットされた文字を利用しているとしています。パスワード中の文字を最大3文字無視するので,極端なケースですが4文字のパスワードなら1文字のパスワードを利用しているのと変わらなくなる場合がありました。恐らくこの問題はPHP 5.3.7RC1がリリースされている頃にPHPプロジェクトに連絡されたと思われます。

攻撃方法

何らかの方法でパスワードデータベースを取得し,総当たり攻撃でパスワードを検出する。

PHP 5.3.7で修正したバグ ─ その2

PHP 5.3.7では長すぎるsaltによるスタックオーバーフローが修正されています。

ext/standard/crypt.c に加えられた変更

        salt[2] = '\0';
 #endif
        salt_in_len = strlen(salt);
+   } else {
+       salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len);
    }

slat_in_lenは

    salt[salt_in_len] = '\0';

とmemcpyでコピーしたsaltパラメータの文字列の終端位置の指定に利用されています。手元のLinux+PHP 5.3.6で試したところ,単純に長いsaltではクラッシュしませんでした。特定のフォーマットが必要なのか,システムのcryptを利用するプラットフォームで発生するのかも知れません。

攻撃方法

攻撃用のPHPスクリプトを実行し,任意のコードを実行させる。

PHP 5.3.7に混入したバグ

セキュリティ強化の一環として,strcatやstrcpyといった安全ではないとされる関数をより安全であるとされるstrlcat,strlcpyに書き換える作業の際にバグが埋め込まれました。このバグにより,crypt_md5を利用している場合,どのようなパスワードを設定していても無効になってしまう重大なセキュリティ上の問題が発生しました。

このバグレポートを見ると分かりますが,肝心のパスワードハッシュが出力されないのでどんなパスワードを入力しても認証できてしまうことになります。

PHP 5.3.7と5.3.8のext/standard/php_crypt_r.c の差分

    /* Now make the output string */
    memcpy(passwd, MD5_MAGIC, MD5_MAGIC_LEN);
    strlcpy(passwd + MD5_MAGIC_LEN, sp, sl + 1);
-   strlcat(passwd, "$", 1);
+   strcat(passwd, "$");
 
    PHP_MD5Final(final, &ctx);

strlcatは第三パラメータでバッファ「全体」の大きさを指定しますが,問題のコードでは第三パラメータが「連結する文字列の長さ」を指定するstrncatを利用している場合に指定すべき⁠1⁠が指定されています。つまり関数の使い方がまったく間違っていたことがこのバグの原因です。仕様の違いを十分考慮しなかったためsaltの部分で文字列がNULL文字(C言語の文字列終端文字)で切れてしまい,肝心のパスワードハッシュが無効になってしまいました。

この連載を読んでいる方はC言語に慣れていない方も多いと思うので補足します。

修正済みstrcatはpasswdバッファーの「最後」⁠$⁠を追加しています。修正前のstrlcatはpasswdバッファーの「先頭」にNULL文字を挿入しています。

strlcatはバッファの長さが1以上の時,文字列がNULL文字で終了することを保証しています。strlcat(passwd,⁠$⁠,1) はpasswdバッファの大きさを1と指定しているので,NULL文字がバッファの先頭に挿入され,結果的にpasswdは空文字列になっています。

攻撃方法

脆弱なcrypt関数でCRYPT_MD5を利用しているシステムにログインする。パスワードは何でもログインできる。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書

コメント

コメントの記入