PHP 5.4.1リリースのポイント

PHP 5.4.1(及びPHP 5.3.11)が2012/4/27(日本時間)にリリースされました。リリースのポイントを以下にまとめます。

ファイルアップロード機能に関する脆弱性の修正(PHP 5.4.1/PHP 5.3.11で修正)

詳細は以下を参照ください:Fixed bug #54374 (Insufficient validating of upload name leading to corrupted $_FILES indices). (CVE-2012-1172)

以前から指摘されていたバグとのことですが、複数ファイルアップロード機能における変数パーサに余分な角括弧([,])の処理に関する不備があり、特殊な入力を行うことで、変数名($_FILES['foo']['name'])をContent-Typeで指定したMIME型で上書きされてしまうというものです。PHPの場合、Content-TypeのMIME型は、$_FILES['foo']['type']として取得可能で、MIME型の推定などに使用されますが、この部分はユーザ側で任意に書き換えることが可能であるため、ディレクトリトラバーサルなどの脆弱性が発生する可能性があります。

上記のURLで紹介される例を以下に紹介します。マルチパート形式のフォームで複数のファイルをアップロードされるシナリオです。

<form action="" method="POST" enctype="multipart/form-data" >
<input type="hidden" name="MAX_FILE_SIZE" value="10000000">
<input type="file" name="pictures[[type]">
<input type="file" name="pictures[[name]">
<input type="file" name="pictures[name][">
<input type="submit" value="submit">
</form>

通常のフォームと異なるのは、name属性に指定している変数名の括弧の数が対応していないことと、typeやnameといったPHP内部変数のハッシュキーと同一の属性を指定していることです。これらを指定することで、PHPの変数パーサの不備をついていることになります。適当なWebブラウザでこのフォームを使ってアップロードを行うと、配列$_FILES['pictures']['name']の要素にapplicaion/octet-streamなどのファイルのMIME型が指定されてしまいます。

例えば、以下のようなコードを使用していた場合、ディレクトリトラバーサル脆弱性が発生します。

move_uploaded_file($_FILES['pictures']['tmp_name'][$key],
                                 $basedir . $_FILES['pictures']['name'][$key]);

ただし、このようなコード自体にはユーザの入力をそのまま使用するという大きな問題があり、より単純な攻撃を受ける可能性があります。ファイル名を直接指定させないホワイトリスト方式の採用やせめてbasename() 関数などでディレクトリトラバーサル攻撃を防ぐ仕組みが必要です。

ファイルアップロード機能のパーサは過去にも数回狙われており、近いところでは、 PHP 5.3.6までの脆弱性PHP 5.3.7で修正されています。
File path injection in PHP ≤ 5.3.6 file upload (CVE 2011-2202)

今回の指摘が問題視されたのは、過去の修正によりPHP側でnameキーについては不正な入力に関するチェックが入るようになっており、それに依存したアプリケーションコードを書いている場合に、typeキーについてはPHPで確認していないために、脆弱性をつかれる可能性があることだと思います。きちんとした脆弱性対策が講じられたアプリケーションでは発生し得ない問題だと思います。

Apache 2.4のサポート (PHP 5.4.1/PHP 5.3.11)

nginx対抗としてリリースされたApacheの最新版バージョン2.4に関して、一部の2.2との差異により、インストール時にエラーを発生するためにパッチが必要でしたが、このバージョンで修正されています。

マルチバイト文字対応コールバック型置換関数の導入(mbstring)

先日発売された弊著の「PHP徹底構築」にも記述していますが、mbstringエクステンションにmb_ereg_replace_callback()という関数がPHP 5.4.1で追加されました。
この関数は、正規表現マッチングした文字列を置換するmb_ereg_replace()関数においてコールバック関数を適用可能とするもので、preg_replace()に対するpreg_replace_callback()の関係に相当します。この種の機能は、従来、eオプションを使用してPHPコードを埋め込むことにより実装されるケースがありましたが、eオプションはPHPコードインジェクションの脆弱性を発生する原因となるため、使用は推奨されておらず、preg_replace()においても将来の廃止の対象となっています。

簡単な使用例を以下に示します。

$str = 'abc 123 #';
echo mb_ereg_replace_callback('(\$+)', function($m) {
 return $m[1].'('.strlen($m[1]).')';
}, $str); // output: abc(3) 123(3) #",;(4) $foo(4)

この関数が普及した時点で、将来のPHPのバージョンでmb_ereg_replace()関数においてもeオプションに警告を出力することも考えられます。

domオブジェクトのデバッグ情報強化

domを操作して、XMLデータを作成するアプリケーションを構築する際のデバッグに便利な機能です。例えば、以下のコードを見てみましょう。

<?php
$xml = <<<EOS
<foo>
 <bar>foobar</bar>
</foo>
EOS;
$dom = new domDocument;
$dom->dynamicProperty = new stdClass;
$dom->loadXML($xml);
print_r($dom);

PHP 5.4.0以前の出力は、以下となります。

DOMDocument Object
(
    [dynamicProperty] => stdClass Object
        (
        )

)

かなり、さみしいですね。
一方、PHP 5.4.1の場合は、以下のように詳細な情報が出力されます。

DOMDocument Object
(
    [dynamicProperty] => stdClass Object
        (
        )

    [doctype] => 
    [implementation] => (object value omitted)
    [documentElement] => (object value omitted)
    [actualEncoding] => 
    [encoding] => 
    [xmlEncoding] => 
    [standalone] => 1
    [xmlStandalone] => 1
    [version] => 1.0
    [xmlVersion] => 1.0
    [strictErrorChecking] => 1
    [documentURI] => /***/tests/
    [config] => 
    [formatOutput] => 
    [validateOnParse] => 
    [resolveExternals] => 
    [preserveWhiteSpace] => 1
    [recover] => 
    [substituteEntities] => 
    [nodeName] => #document
    [nodeValue] => 
    [nodeType] => 9
    [parentNode] => 
    [childNodes] => (object value omitted)
    [firstChild] => (object value omitted)
    [lastChild] => (object value omitted)
    [previousSibling] => 
    [attributes] => 
    [ownerDocument] => 
    [namespaceURI] => 
    [prefix] => 
    [localName] => 
    [baseURI] => /***/tests/
    [textContent] => 
 foobar

)