T.Teradaの日記

2006-12-24

[]PHPでの入力値チェックのすり抜け 00:23 PHPでの入力値チェックのすり抜けを含むブックマーク

Webアプリケーションでは、外部からの変数に対して、形式チェック(Validation)を行ないます。PHPでこれを行なう場合に、ありがちなミスをいくつか挙げてみました。

この日記は、がるさんの日記に触発されて書いたもので、いくつかの例を引用しています。

がるの健忘録(2006/11/08) - 素晴らしき自動的な世界〜或いは「型のない」世界〜

型の問題

数値と文字列の比較
<?php
$input = "2'; DELETE FROM hoge; --";
if ($input == 2) {
    // ↑TRUEと評価される

がるさんの日記で紹介されていた例に、手を加えたものです。

if文中の式がTRUEになるのは、PHPの「==」演算子が、数値型と文字列型変数を比較する際に、文字列を(かなり強引なやり方で)数値型に変換するからです。変数の比較は、同じ型同士で行なうのが無難だと思います。

<?php
$whitelist = array(1, 2);
$input = "2'; DELETE FROM hoge; --";
if (in_array($input, $whitelist)) {
    // ↑TRUEと評価される

がるさんの日記のコメント(byかずくんさん)を、ほぼそのまま引用したものです。これは、先ほどの例と同じ原理で問題が生じています。

in_array関数の三番目の引数にTRUEを与えると、型も考慮した一致判定を行ないます(そうすると、上記のプログラムは正常系も動かなくなりますが)。

なお、array_search関数も同様の動作をします。

文字列同士の比較
<?php
// DBから取得したパスワード
$db_pass = "01203";
if ($input_pass == $db_pass) {
    // ↑$input_passが"01203"以外でも
    // TRUEになる場合がある

これは、がるさんの日記に私がコメントしたものを、アレンジしたものです。

$input_passが、"1203"、"0x4b3"、"+12030.0e-1"などの値の場合に、if文中の式がTRUEになります。これは、PHPの「==」演算子が文字列同士を比較する際に、左右とも数値とみなせる場合は、数値にして比較するからです。

パスワードなどの厳密な取扱いを要する文字列では、strcmp関数や「===」演算子を使いましょう。

正規表現

ereg系関数
<?php
// \0はNUL文字
$input = "2\0'; DELETE FROM hoge; --";
if (ereg("^[0-9]$", $input)) {
    // ↑TRUEと評価される

ereg系関数がバイナリセーフではないことを利用した攻撃で、割と広く知られている方法です。

バイナリセーフでない関数は、他にもいくつかあります(PHP と Web アプリケーションのセキュリティについてのメモを参照)。

これには、NULバイトを事前に排除する(含まれていたらエラーにする)対策が良いと思います。その上で、バイナリセーフで、高速・多機能なPCRE関数(preg_matchなど)を使いましょう。

なお、mb_ereg系関数はバイナリセーフです。

preg系関数
<?php
$input = "tera\n"; // \nはLF文字
if (preg_match("/^[a-z]+$/", $input)) {
    // ↑TRUEと評価される

非常にありがちなコードですが、問題が潜んでいます。

「$」は、末尾のLF文字の直前にもマッチするため、末尾にLFが付いている文字列を通してしまうのです(Perlも同じのはず)。

SQLインジェクションやXSSの心配はありませんが(多分)、LF文字が特殊文字としての機能を持つ場面はあります。例えば、LFがレコードセパレータのデータファイルや、system関数などで実行するshellコマンドなどに、うかつにLF文字を含む文字列を挿入すると、エラーが発生するかもしれません。

あるいは、会員制サイトで、"tera"に対して"tera\n"という会員IDを作成し、見た目上(あるいはシステム上)で両者を混同させるような攻撃もありえます。

PHPでは、パターン修飾子「D」を使うことで、この問題を回避できます(事前に変数をtrimしておくことでも避けられます)。

まとめ

  • 型を意識する
    PHPの「型の自動変換」はトラブルの元になります。PHPは、開発者が変数の型を意識する必要のある言語です(皮肉なことに、型に厳密な言語よりも)。

世間様でよく「PHPは型のない言語」だとかなんとかいう風評が流されておりますが。とんでもハップンでございます。

PHPは、素晴らしく「型の厳密な」言語でございますっていうか「型を厳密にプログラマが意識しておかないといかん言語」でございます。

そうでなければ何故に gettype などという関数が用意されているとおっしゃるのでしょうか? is_int()が、is_string()が、用意されているとお思いでしょうか?

がるの健忘録(2006/11/08) - 素晴らしき自動的な世界〜或いは「型のない」世界〜

  • エスケープ処理と形式チェックは別物
    上記では、SQLインジェクションを匂わす例をいくつか挙げました。文脈上エスケープ処理が必要な場面で、形式チェックに頼ってエスケープ処理を省略するのは、良い習慣ではないと思います(SQLに限らず)。上記例のようなものを含めて、形式チェックには思ったよりも実装ミスが多いからです。
    またそれ以上に、通常の形式チェック処理は、エスケープ処理とは異なる観点・目的で行なうものだからです。SQLインジェクション除けにはエスケープ*1、業務仕様外のデータの登録防止などには形式チェックを行ないます。

[]インプラント手術を受けてきた 00:52 インプラント手術を受けてきたを含むブックマーク

先日、都内の病院でインプラントの手術を受けてきた。

所要時間は1〜2時間程度で、通常の歯科治療と余り変わらない。

通常と違うところは、顔と体にビニールの覆いを被せられて、手術室で治療したことくらいか。

まずは、一本の抜歯(歯根のみ)をした。その歯根の状態が思ったより悪かったようで、骨を侵食しているようなことを言われた。もっと早く病院に来ればよかったと言われたが、もう遅い。

続いて、インプラントの土台を作った。手術中は何も見えないが、ドリルでゴリゴリと骨を削ったり、トンカチのようなものでインプラントの土台を頭蓋骨に打ち込まれたりした。当然麻酔をしているので、痛みはないけども、余り気持ちのいいものではない。削った骨は、人工の骨の素と混ぜて、上あごの補強に使ったらしい(GBR法?)。

結局、予定通りの長さのものが無事に入れられたようだ。手術はとりあえず成功ということらしい。

麻酔が覚めてから、激痛というほどではないが、痛みが続いている。数日たった今でもそれなりに痛みがあり、結構なストレスだ。手術をした病院からもらった痛み止めは2錠だけだったので(何でだろう)、以前骨折した際にもらって余っていた痛み止めを飲んでしのいでいる。

ちゃんと回復しているのか、非常に不安だ。早く痛みが引いて、通常の生活に戻れればいいなと思う。

*1:可能ならPrepared Statementを使う。