▼2015年2月13日金曜日PHPのbasename関数は不正な文字エンコーディングをチェックしない昨日のエントリにて、PHPのbasename関数はマルチバイト文字を扱えることを説明しましたが、このブログの読者であれば、きっとbasename関数は不正な文字エンコーディングについてどの程度チェックするのかという疑問が生じたことでしょう(きっぱり)。実はbasename自体は、不正な文字エンコーディングをチェックせず、垂れ流してしまいます。その理由をbasenameのソースコードで確認してみましょう。以下は、basename関数の実装の一部です。quit_loop;php_mblen関数はmblen(3)のラッパーです。上記のソースでは、mblenが-1を返した場合は、inc_len=1として正常な1バイト文字と見なして処理を継続しています。文字に応じた処理コメントにあるように、不正な多バイト文字はスキップする、すなわち除去(フィルタリング)されます。同じmblen関数を使っていても、basename関数とescapeshellarg関数では、不正な文字エンコーディングに対する対処方法が違っています。basename関数は、不正な文字エンコーディングをエラーとせず、結果の中に含めてしまいます。不正な文字エンコーディングの影響の考察(Windowsの場合)basename関数が不正な文字エンコーディングのチェックをしていないことによるセキュリティ上の影響はないでしょうか。具体的に確認するために、まずWindowsの場合について検討します。エンコーディングされているとします。来る必要があり、これは「不正な文字エンコーディング」にはなりえません。余計な文字がはさまっているため、攻撃パターンを形成しないと思われます。不正な文字エンコーディングの影響の考察(Linuxの場合)次にLinuxの場合について考えます。この場合、basename関数はUTF-8の冗長表現を通してしまいます。これを検証するためのスクリプトを以下に示します。UTF-8の2バイト表現にしたものです。PHP_EOL;出力は下記となります。c0afがそのまま出力されていることがわかります。2e2ec0af616161UTF-8の冗長表現が許可されるというと、NimdaワームやTomcatの脆弱性CVE-2008-2938を思い出す人も多いと思います。「それは問題ではないか」と思うところですが、現実には問題になるケースはほとんどないと考えられます。その理由は下記のとおりです。mb_convert_encodingとiconvはどちらもUTF-8の冗長表現をエラーにするかフィルタリングするので、攻撃文字列は形成されないということで、basename関数が冗長なUTF-8エンコーディングを許容しても、実害が出るケースはほとんどないと考えられます。実害があるとすると、独自実装の脆弱性のある文字エンコーディング変換機能を利用している場合ですが、その場合でも文字エンコーディング変換後にbasename関数を通すという正しい手順を踏んでいれば、問題は顕在化しません。緩和策basename関数は不正な文字エンコーディングを許容することが分かりましたが、これによる実害はほとんどなさそうです。外部から与えられたファイル名で新規にファイルを作成する場合は、変なファイル名のファイルができてしまいます。いずれにせよ、以下をアプリケーションの仕様として決めておくとよいでしょう(再掲)。ファイル名に用いる文字の種類ファイル名を表現する文字エンコーディングファイル名の長さの最小値・最大値そして、以下を推奨します。文字エンコーディングの変換はbasenameを通す前に行うことbasename関数を呼ぶ前にlocaleを設定することファイル名の仕様を決めるファイル名が(文字エンコーディングを含め)仕様を満たすかどうかバリデーションにより確認するまとめPHPのbasename関数が不正な文字エンコーディングを許容してしまうことを説明しました。この問題は一応bug#68773として報告済みですが、報告から1ヶ月以上たってもアサインもされていませんので、少なくともすぐに修正される可能性は薄そうです。実害もあまりなさそうですが、念のためバリデーションにより文字エンコーディングのチェックをしておくと安心です。PHPの文字列は単なるバイト列ですので、一般論として、アプリケーションの開始時に文字エンコーディングのチェックをしておくことにより、不正な文字エンコーディングの文字を弾いておくことをお勧めします。アプリケーションの前提条件を満たしていない入力を予め除外しておくことでアプリケーションの安定動作のために寄与します。basenameの現在の実装は少々いただけないと考えます。返しているのに、そのエラーを「なかったことに」しているからです。シェルのエスケープを行うescapeshellargの方は不正な文字エンコーディングをフィルタリングしているわけで、同じPHPの中で一貫性のない挙動というのも(PHPらしいといえばそれまでですが)よくないように感じました。