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

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

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

PHP 5.3.7で修正されたcrypt_blowfishの脆弱性

PHP 5.3.7で入り込み,PHP 5.3.8で修正されたcrypt_md5の脆弱性は,攻撃が可能な場合は非常に危険な脆弱性でしたが,非常に分かりやすい脆弱性でした。一方,crypt_blowfish自体の脆弱性は少々分かりづらい脆弱性です。

レガシーPHPのcrypt関数

PHP 5.3からPHPのcrypt関数の仕様は大きく変わりました。PHP 5.3までのPHPのcrypt関数は単純にシステムが提供するcryptライブラリを呼び出していました。

次のコードはPHP 5.2.17のcrypt関数が実際にライブラリのcryptを呼び出している部分です。

#if defined(HAVE_CRYPT_R) && (defined(_REENTRANT) || defined(_THREAD_SAFE))
    {
#if defined(CRYPT_R_STRUCT_CRYPT_DATA)
        struct crypt_data buffer;
        memset(&buffer, 0, sizeof(buffer));
#elif defined(CRYPT_R_CRYPTD)
        CRYPTD buffer;
#else
#error Data struct used by crypt_r() is unknown. Please report.
#endif

        RETURN_STRING(crypt_r(str, salt, &buffer), 1);
    }
#else
    RETURN_STRING(crypt(str, salt), 1);
#endif

コードからも分かるようにC言語のマクロ(#で始まるプリプロセッサで処理される部分)でシステムが持っているcryptの機能によって呼び出される関数が異なることが分かります。PHPをC言語のソースからコンパイルする場合にコンパイル時のオプションを指定する./configureスクリプトにはcrypt関数の動作に関するオプションはありません。しかし,システムが提供しているcryptの機能はどのようなものか自動的に判別するようになっていました。

例えば,salt(同じパスワードでも異なるハッシュとなるように味付けする文字列)をサポートする場合は適切なsaltを自動生成するコードは次のようになっています。

    if(!*salt) {
#if PHP_MD5_CRYPT
        strcpy(salt, "$1$");
        php_to64(&salt[3], PHP_CRYPT_RAND, 4);
        php_to64(&salt[7], PHP_CRYPT_RAND, 4);
        strcpy(&salt[11], "$");
#elif PHP_STD_DES_CRYPT
        php_to64(&salt[0], PHP_CRYPT_RAND, 2);
        salt[2] = '\0';
#endif
    }

MD5 CryptやDES Cryptをサポートするシステムの場合はsaltが自動生成されます。このコードからも分かるように,PHP 5.3より古いレガシーPHPのcrypt関数はポータブルではなく,システムに依存する関数でした。

PHP 5.3のcrypt関数

PHP 5.3のcrypt関数から,システムがcryptライブラリを提供しない場合,PHPの実装が利用されるようになりました。PHP 5.2以前のソースではcrypt.cしか提供されませんが,PHP 5.3.8のソースではcrypt関数関連のソースファイルは以下のファイルが提供されています。

  • crypt.c         crypt_blowfish.h  crypt_freesec.h  
    crypt_sha512.c crypt_blowfish.c crypt_freesec.c
    crypt_sha256.c

crypt.cの実装も大きく変わり,cryptライブラリを呼び出す処理は以下のようになりました。

PHP 5.3.8のext/standard/crypt.cより抜粋

#if PHP_USE_PHP_CRYPT_R
    {
        struct php_crypt_extended_data buffer;

        if (salt[0]=='$' && salt[1]=='1' && salt[2]=='$') {
            char output[MD5_HASH_MAX_LEN];

            RETURN_STRING(php_md5_crypt_r(str, salt, output), 1);
        } else if (salt[0]=='$' && salt[1]=='6' && salt[2]=='$') {
            const char sha512_salt_prefix[] = "$6$";
            const char sha512_rounds_prefix[] = "rounds=";
            char *output;
            int needed = (sizeof(sha512_salt_prefix) - 1
                        + sizeof(sha512_rounds_prefix) + 9 + 1
                        + strlen(salt) + 1 + 43 + 1);
            output = emalloc(needed * sizeof(char *));
            salt[salt_in_len] = '\0';

            crypt_res = php_sha512_crypt_r(str, salt, output, needed);
(中略)
        } else {
            memset(&buffer, 0, sizeof(buffer));
            _crypt_extended_init_r();

            crypt_res = _crypt_extended_r(str, salt, &buffer);
            if (!crypt_res) {
                if (salt[0]=='*' && salt[1]=='0') {
                    RETURN_STRING("*1", 1);
                } else {
                    RETURN_STRING("*0", 1);
                }
            } else {
                RETURN_STRING(crypt_res, 1);
            }
        }
    }
#else

# if defined(HAVE_CRYPT_R) && (defined(_REENTRANT) || defined(_THREAD_SAFE))
    {
#  if defined(CRYPT_R_STRUCT_CRYPT_DATA)
        struct crypt_data buffer;
        memset(&buffer, 0, sizeof(buffer));
#  elif defined(CRYPT_R_CRYPTD)
        CRYPTD buffer;
#  else
#    error Data struct used by crypt_r() is unknown. Please report.
#  endif
        crypt_res = crypt_r(str, salt, &buffer);
        if (!crypt_res) {
                if (salt[0]=='*' && salt[1]=='0') {
                    RETURN_STRING("*1", 1);
                } else {
                    RETURN_STRING("*0", 1);
                }
        } else {
            RETURN_STRING(crypt_res, 1);
        }
    }
# endif

このコードから分かるようにPHP 5.3のcrypt関数でもシステムが提供するcryptライブラリ(実際にはリエントラントなcrypt_r)が呼び出されていることが分かります。PHP 5.3になってcrypt関数のポータビリティはかなり向上しましたが,それでもポータブルな関数とは言えません。

著者プロフィール

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

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

URLhttp://blog.ohgaki.net/

著書