PHP 5.
PHP 5.
crypt関数のバグ
最近のPHPで行われたcrypt関数のバグ修正は3つあります。
PHP 5.3.7で修正したバグ ─ その1
PHP 5.
この脆弱性は2011/
脆弱性があるcrypt_
- 攻撃方法
何らかの方法でパスワードデータベースを取得し、
総当たり攻撃でパスワードを検出する。
PHP 5.3.7で修正したバグ ─ その2
PHP 5.
salt[2] = '\0';
#endif
salt_in_len = strlen(salt);
+ } else {
+ salt_in_len = MIN(PHP_MAX_SALT_LEN, salt_in_len);
}
slat_
salt[salt_in_len] = '\0';
とmemcpyでコピーしたsaltパラメータの文字列の終端位置の指定に利用されています。手元のLinux+PHP 5.
- 攻撃方法
攻撃用のPHPスクリプトを実行し、
任意のコードを実行させる。
PHP 5.3.7に混入したバグ
セキュリティ強化の一環として、
このバグレポートを見ると分かりますが、
/* 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は第三パラメータでバッファ
- 攻撃方法
脆弱なcrypt関数でCRYPT_
MD5を利用しているシステムにログインする。パスワードは何でもログインできる。
PHP 5.3.7で修正されたcrypt_blowfishの脆弱性
PHP 5. PHP 5. 次のコードはPHP 5. コードからも分かるようにC言語のマクロ 例えば、 MD5 CryptやDES Cryptをサポートするシステムの場合はsaltが自動生成されます。このコードからも分かるように、 PHP 5. crypt. このコードから分かるようにPHP 5. 筆者が普段利用しているMomonga Linux 7で./ DESとMD5がサポートされていることが分かります。実際にどのような設定になったかC言語のマクロで確認します。 ソースコードだけ見ると、 が決定的です。この定義があると先に紹介したext/レガシーPHPの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
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
}
PHP 5.
crypt.
crypt_
crypt_#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
Linuxの場合
checking for crypt in -lcrypt... (cached) yes
checking for standard DES crypt... (cached) yes
checking for extended DES crypt... (cached) no
checking for MD5 crypt... (cached) yes
checking for Blowfish crypt... (cached) no
checking for SHA512 crypt... (cached) no
checking for SHA256 crypt... (cached) no
#define HAVE_CRYPT 1
/* Define to 1 if you have the <crypt.h> header file. */
#define HAVE_CRYPT_H 1
/* Define to 1 if you have the `crypt_r' function. */
/* #undef HAVE_CRYPT_R */
/* Whether the system supports BlowFish salt */
#define PHP_BLOWFISH_CRYPT 1
/* Whether the system supports extended DES salt */
#define PHP_EXT_DES_CRYPT 1
/* Whether the system supports extended DES salt */
#define PHP_EXT_DES_CRYPT 1
/* Whether the system supports SHA256 salt */
#define PHP_SHA256_CRYPT 1
/* Whether the system supports SHA512 salt */
#define PHP_SHA512_CRYPT 1
/* Whether the system supports standard DES salt */
#define PHP_STD_DES_CRYPT 1
/* Whether PHP has to use its own crypt_r for blowfish, des and ext des */
#define PHP_USE_PHP_CRYPT_R 1
/* Whether PHP has to use its own crypt_r for blowfish, des and ext des */
#define PHP_USE_PHP_CRYPT_R 1
この例はMomonga Linuxの例ですが、
if test "$ac_cv_crypt_blowfish" = "no" || test "$ac_cv_crypt_des" = "no" || test "$ac_cv_crypt_ext_des" = "no" || test "x$php_crypt_r" = "x0"; then
と定義されているため、
PHPプロジェクトのCRYPT_BLOWFISHの解説ページ
PHPプロジェクトはcrypt関数のバグについて解説ページを用意しています。
筆者による意訳を載せておきます。
PHP 5.
5.
PHP 5.
$2y$プレフィックスを持つ場合、
まとめ:8bit目が設定されていない文字エンコーディングを利用しているパスワードの場合、
しかし、
これまでの解説で何故アライメント
Linux系OSでは随分前から認証にはPAM
BSD系のOSやSoralisではcryptのサポートが充実しています。このため、
crypt関数には色々問題があったのですべては書ききれませんが、
PHP 5.
3.0以前はcrypt関数はシステム依存な関数であり、 スレッドセーフではない場合も多くあった PHP 5.
3.0から5. 3.6までは多くのプラットフォームでcrypt関数はまともに動作していなかった PHP 5.
3.7以前のPHPでもASCII文字 (8ビット目が0) だけを利用していれば問題は発生しない 正常なUTF-8エンコーディングでは脆弱にならない
(未検証)
つまり
Crypt BlowfishやMD5 Cryptのバグは攻撃できるのか?
筆者はcrypt関数はシステム依存の関数であったので使うべきでない関数として分類していました。ほとんどのLinuxシステムでMD5 Cryptしか利用できない
Google Code Searchで
- phpassを利用している主なPHPアプリケーション
- WordPress 2.
5+ - Drupal 7+
- SquirrelMail
- WordPress 2.
PHP 5.
Google Code Searchで検索しヒットしたコードの上位200くらいを見た限りでは、
脆弱性の影響評価は一般に
- 影響度 = 攻撃が成功した場合のダメージ × 攻撃が成功する確率 × 攻撃経路が存在する割合
と考えます。攻撃経路の存在は非常に重要で、
Crypt MD5でパスワードが無効になるバグの
自分の利用しているシステムへの影響を評価する場合、
十分に知識を持っていると思われる経験者でも、
なぜMD5 Cryptのバグが混入したか?
MD5 CryptのバグとCrypt Blowfishのバグは関連しているように見えるかも知れませんが、
第一の原因はリリースプロセス中にソースコードの安全性強化を目的にCの文字列関数をより安全と考えられている関数に置き換える作業をしたことです。
第二の原因はMD5 CryptのバグはPHPのテストコードでエラーとして検出していたにも関わらず見逃されたことです。折角のテストコードも実行して確認しなければ意味がありません。
上記の二つが原因ですが、
まとめ
PHP 5.
MD5 Cryptの問題はユニットテストを実行し確認すれば簡単に発見できる問題でした。PHPのリリース候補は広く公開されています。オープンソースはボランティアによって支えられています。余裕のある方はリリース候補のソースコードをダウンロードし、
Google Code Searchでcrypt関数が利用されているコードを検索した結果を見ていて、