なぜeregがダメなのかってこと

php5.3からは「Deprecated: Function ereg() is deprecated in ・・・・」とか言われるようになったり、PHP6以降では使えなくなるよって話のereg関数。
「この機能を使用しないことを強く推奨します。」ってまで言われてしまっているのはなぜか。誰も詳しく教えてくれなかったので、少しまとめてみました。

バイナリセーフじゃないってこと

一言でいえばそういうことになります。 バイナリセーフとはNULLバイト文字’¥0’等も正しく処理されることです。ereg関数はバイナリセーフな関数ではないのでNULLバイト文字を正しく処理できません。 NULLバイト文字とは、C言語等では文字列の終了を表します。例えばC言語での文字列は、char型のポインタのアドレスからNULLバイト文字までで表現されています。

// C言語の場合
char *str = "abcdefg";
printf("%s", str); // abcdefgと出力される

// NULLバイト文字を代入
str[3] = '\0';
printf("%s", str); // abcと出力される

2行目でchar型のポインタに”abcdefg”という文字列リテラルを代入しています(文字列リテラルはその先頭ポインタを返します)。このとき、ポインタの指すアドレスからは”abcdefg”+’¥0’の合計8byteが格納されています。 3行目の処理ではポインタのアドレスから’¥0’までが処理され、”abcdefg”となります。 そして6行目で、ちょうど’d’の文字に当たる場所「str[3]」に’¥0’を代入してみました。すると7行名では、格納されている領域の大きさにかかわらず’¥0’までで処理がストップしてしまい”abc”より後ろの文字列が処理できなくなってしまいました。 一方、phpの場合は通常nullバイト文字があろうが無かろうが、文字列は最後まで処理します。

// phpの場合
$str = "abcdefg";
var_dump($str); // string(7) "abcdefg"

$str[3] = "\0";
var_dump($str); // string(7) "abcefg"

phpはシングルクォートでNULLバイト文字を入れようとするとエスケープシーケンスしてくれなくなるので、ダブルクォートで代入します。 またvar_dumpで結果を見ているのは文字列長も知りたかったからです。3行目の結果は”abcdefg”で7文字になっています。6行目の結果では”abcefg”となっていますが、7文字として数えられています。これはNULLバイト文字も1文字として数えられたからです。 このような処理がバイナリセーフであるということです(たぶん)。 つまり、eregはバイナリセーフじゃないので、C言語のような処理をしてしまうということです。

じゃあ、eregの何がダメなのか。

// 半角数字のみをチェックする関数
function isNumber($str)
{
    return ereg('^[0-9]+$', $str);
}

ereg関数を使って半角英数をチェックする関数を作ってみました。 この関数を使って実際に値のチェックを行います。

$str = "123";
var_dump(isNumber($str)); // int(1)

$str = "123abc";
var_dump(isNumber($str)); // bool(false)


結果は期待通りです。では以下の場合はどうでしょうか。

$str = "123\0abc";
var_dump(isNumber($str)); // int(1)

文字列”$str”には半角数字以外が含まれているにも関わらず、「マッチした(半角英数のみ)」という返り値が帰ってきました。 ereg関数はバイナリセーフな関数ではないため、’¥0’以降のデータが処理できず、”123″のみで判断してしまったのです。 このようにereg関数は’¥0’以降のデータを処理できないことを利用され、’¥0’以降に不正なコードを入力してアプリケーションの誤作動を狙った攻撃の対象になりかねないのです。

実際の攻撃の例

たとえばGET値から受け取ったIDを引数にしてユーザーのデータを取得するSQLを作成する関数があったとします。

function createQuery($id)
{
    // idは半角数字のみ
    if(!ereg('^[0-9]+$', $id))
    {
        return false;
    }
    return "SELECT * FROM user WHERE id = ".$id.";";
}

echo createQuery($_GET['id']);
URL
http://sawara.me/?id=123
実行結果
SELECT * FROM user WHERE id = 123;

URLから受け取ったidでSQLを作成することができました。

eregで半角数字チェックを行っていますので、数字以外が入力された場合は処理されません。

URL
http://sawara.me/?id=abc
実行結果

実行結果には何も表示されませんでした。実際にはfalseが返ってきていますので、falseの場合はSQLを実行しなければいいわけです。 ではNULLバイト文字を含めたURLで結果を見てみます。NULLバイト文字はURLエンコードすると’%00’になります。

URL
http://sawara.me/?id=123%00abc
実行結果
SELECT * FROM user WHERE id = 123abc;

これを実行してしまうとSQLエラーになります。 エラーになるだけならいいのですが、URLにSQL文なんかが含まれていると・・・

URL
http://sawara.me/?id=123%00;DROP+TABLE+user
実行結果
SELECT * FROM user WHERE id = 123;DROP TABLE user;

結論!ereg関数は使うべからず!

実際なんとなく見逃してしまっていたeregですが、preg_match等のバイナリセーフな関数に置き換えておくのが吉です。

function createQuery($id)
{
    // こんな感じで
    if(!preg_match('/^[0-9]+$/', $id))
    {
        return false;
    }
    return "SELECT * FROM user WHERE id = ".$id.";";
}

ちなみにバイナリセーフでない関数には下記のものがありますので、これらも注意が必要です。

  • ereg_replace
  • eregi_replace
  • eregi
  • split
  • spliti
  • sql_regcase

コメントを残す