いえらぶ > ブログ > 記事詳細

  • WEB
  • 2015-7-28
  • faebook
  • ツイッター
  • グーグル+
  • bookmark
  • LINEで送る
  • pocket

今更だけど、なんでeregがダメかっていうことを説明するよ。

最近ちょっと古いサービスのPHPのバージョンを上げるという案件がございまして。

Deprecated: Function ereg() is deprecated in ・・・・

出たなereg関数、お前はとっくに非推奨になっているはず。誰だよこんなソースコード書いたのは!?って犯人探しをすると大体の場合は過去の自分だったりするのでやめておきます。

鰆目

プログラムは動けばいい。そんなふうに考えていた時期が俺にもありました。

さて、PHPの正規表現にはPOSIX拡張Perl互換というのがあって、このPOSIX拡張の方の関数がver5.3以降で軒並み非推奨になっています。

別にPOSIX拡張ってダサいよねっていう理由で非推奨になっているわけではないです。ereg関数を使った場合に発生する脆弱性が問題なんですね。

今回はereg関数がなぜダメなのかってことを、今更ながら改めて説明したいと思います。

eregを使ったバリデート

例えばereg関数を使ってバリデート処理を実装したとします。以下の処理はGETで受け取ったidをバリデートし、userテーブルから情報を取得するSQLを生成します。

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

var_dump(getUserQuery($_GET['id']));

URL

http://localhost/?id=123

実行結果

string(34) "SELECT * FROM user WHERE id = 123;"

っていう塩梅になります。idに半角数字以外を渡すと、こうなります。

URL

http://localhost/?id=123abc

実行結果

bool(false)

期待通りですね。これでバリデートの機能としては問題ないように思えます。

バリデートを破る攻撃

しかし、以下のようなURLで実行すると

http://localhost/?id=123%00abc

実行結果

string(38) "SELECT * FROM user WHERE id = 123abc;"

ファッ!?半角数字以外もすり抜けちゃいました。

更に以下のようなURLを実行されることによって、

http://localhost/?id=123%00+OR+1=1

実行結果

string(42) "SELECT * FROM user WHERE id = 123 OR 1=1;"

(☝ ՞ ՞)☝ファーーーーーー!!??

よくあるSQLインジェクションの例みたいになってしまいました。これで個人情報漏れまくりです。

なぜ、半角英数のみしか許可されないバリデートが、半角英数以外も通してしまったのでしょうか。

それはチェックするidにNULLバイトが含まれていたからです。

NULLバイトとは

まず、NULLバイトは0のエスケープシーケンス「\0」で表現されます。URLからは「%00」で表現できます。上記URLにも含まれていますね。

そしてNULLバイトはC言語では文字列の終了を意味します。C言語では文字列を持つ変数の実体はその文字列の先頭のポインタであり、その先頭のポインタからNULLバイトまでで文字列が表現されます。

C言語の場合

char *str = "abcdefg";
printf("%s", str); // abcdefg と出力される

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

ではPHPではNULLバイトはどうなのかって言うと、別に文字列の終了を意味するものではありません。

PHPの場合

<?php
$str = "abcdefg";
var_dump($str); // string(7) "abcdefg" と出力される

$str[3] = "\0"; // NULLバイトを代入
var_dump($str); // string(7) "abcefg" と出力される

しかしereg関数のような特定の関数はNULLバイトを文字列の終了として扱ってしまうため、NULLバイト以降は判断されません。

<?php
var_dump(ereg('^[0-9]+$', "123")); // int(1)
var_dump(ereg('^[0-9]+$', "123abc")); // bool(false)
var_dump(ereg('^[0-9]+$', "123\0abc")); // int(1)

この問題を回避するためにはバイナリセーフな関数に置き換えてやる必要があります。

バイナリセーフな関数に置き換えよう!

バイナリセーフとはNULLバイトも正常に扱えますよってことです。Perl互換の正規表現の関数群がまさにそれですね。eregはpreg_matchに置き換えます。

<?php
function getUserQuery($id)
{
    // 半角数字のみ許可
    if(!preg_match('/^[0-9]+$/', $id))
    {
        return false;
    }
    return "SELECT * FROM user WHERE id = " . $id . ";";
}

var_dump(getUserQuery($_GET['id']));

URL

http://localhost/?id=123%00+OR+1=1

実行結果

bool(false)

preg_matchに置き換えることで正規表現のパターンも書き換える必要がありますが、これでNULLバイト以降も判断されて期待通りのバリデート結果となりました。

最後に

PHPはファイルシステムの操作にC言語の関数を使用しているので、今回紹介した例以外にもNULLバイトを文字列の終了として扱ってしまう場合があるようです。

こういった問題を事前に防ぐために、外部から入力されるパラメータに対して常に適切なチェックを行うという心構えがプログラマには必要になってきます。

そんなの関係ないって思っているそこのアナタ!~E_DEPRECATEDでエラーを消してましたね??これを機にバイナリセーフな関数に置き換えちゃいましょう!


ページトップ

戻る