なぜPHPアプリにセキュリティホールが多いのか?

第38回 MOPS:PHPにおけるコード実行(2)

この記事を読むのに必要な時間:およそ 3.5 分

コード実行可能だが稀なケース

ob_start関数はフラッシュするためのコールバック関数を登録可能です。次のコードはsystem('uname')を実行します。

出力バッファを利用するコード実行

<?php
$foobar = 'system';
ob_start($foobar);
echo 'uname';
ob_end_flush();
?>

$foobarと出力バッファの内容を制御できる場合はコード実行攻撃を行えます。

assert関数はeval関数と同様の動作をするので簡単に攻撃可能ですが,脆弱性となることは稀です。

assert関数を利用するコード実行

<?php
$foobar = 'system("uname")';
assert($foobar);
?>

配列関数の多くがコールバック関数をサポートしています。脆弱性となるケースは稀ですがコールバック関数を利用してコード実行が可能です。

<?php
$evil_callback = 'phpinfo()';
$some_array = array(0, 1, 2, 3);
$new_array = array_map($evil_callback, $some_array);
?>

$evil_callbackが制御可能な場合,phpinfo関数など攻撃に利用可能な関数を呼び出せます。

XML関数やSTREAM関数もコールバック関数を利用可能であるため配列関数と同様の手法で攻撃される可能性があります。

コールバックをサポートする関数

ob_start()

preg_replace()
preg_replace_callback()

array_map()
usort(), uasort(), uksort()
array_filter()
array_reduce()
array_diff_uassoc(), array_diff_ukey()
array_udiff(), array_udiff_assoc(), array_udiff_uassoc()
array_intersect_assoc(), array_intersect_uassoc()
array_uintersect(), array_uintersect_assoc(), array_uintersect_uassoc()
array_walk(), array_walk_recursive()

xml_set_character_data_handler()
xml_set_default_handler()
xml_set_element_handler()
xml_set_end_namespace_decl_handler()
xml_set_external_entity_ref_handler()
xml_set_notation_decl_handler()
xml_set_processing_instruction_handler()
xml_set_start_namespace_decl_handler()
xml_set_unparsed_entity_decl_handler()

stream_filter_register()
set_error_handler()
set_exception_handler()
register_shutdown_function()
register_tick_function()

その他の手法

次のPoCは実際には動作しませんが,オブジェクトのデストラクタを利用した攻撃方法を解説するコードです。$varに攻撃用のコードを埋め込み,unserialize関数の引数とすれば任意コード実行が可能となります。

<?php
class Example {
   var $var = '';
   function __destruct() {
      eval($this->var);
   }
}
unserialize($_GET['saved_code']);
?>

この攻撃を成功させるには攻撃用のクラス定義を読み込ませるか,既存のクラス定義を利用する必要があります。この攻撃を成功させることは非常に難しいですが,実際にこの脆弱性を利用した攻撃が知られています。

MOPBでStefan Esser氏はunserialize関数のメモリエラーを利用した攻撃方法を紹介すると同時に,シリアライズした如何なるデータもユーザから受け取るべきではない,と指摘していました。当時はまだこのような攻撃手法は知られていませんでしたが,ハンドラをサポートしたデータ型のデータを外部から受け取るリスクを考慮しての意見だと思います。

複数のデータやオブジェクトや配列を文字列として保存できるシリアライズ機能は便利ですがリスクが存在することも知っておくとよいでしょう。

Gerkis氏が参考文献としたURLの一覧

  1. http://www.hardened-php.net/suhosin/
    Suhosin, advanced protection system for PHP
  2. http://projects.webappsec.org/Remote-File-Inclusion
    explanation of RFI
  3. http://tools.ietf.org/html/rfc2397
    The ⁠data⁠⁠ URL scheme
  4. http://www.php.net/manual/en/wrappers.data.php
    Data (RFC 2397), PHP manual
  5. http://www.ush.it/2008/08/18/lfi2rce-local-file-inclusion-to-remote-code-execution-advanced-exploitation-proc-shortcuts/
    how LFI can lead to RCE
  6. http://www.exploit-db.com/papers/260
    how LFI can lead to RCE (2)
  7. http://www.sektioneins.com/en/advisories/index.html
  8. まとめ

    Gerkis氏は,ソースコード中にコードを埋め込む場合はかならず悪意があるコードが実行されることを考え,決してユーザを,たとえ管理者であっても信用してはならないとしています。ユーザ入力を正しくエスケープすることは非常に難しく,見落とす可能性は非常に大きいので,ブラックリスト型の対策は常に悪い方法であると認識すべきだとしています。筆者もGerkis氏の意見とまったく同じです。

    安全なソースコードを書くためにはホワイトリスト型の対策でユーザ入力を検査すべきで,ホワイトリスト型のほうがよほど簡単に十分なセキュリティを確保できます。

    開発者にとって便利な機能はセキュリティリスクとなることがよくあります。セキュリティとは利便性とのトレードオフの関係にあるので当然です。動的言語は非常に便利な反面,リスクがあることを理解して使う必要があります。

    次回はMOPSで紹介された静的にソースコードを分析する脆弱性スキャナ(RIPS)を紹介します。

著者プロフィール

大垣靖男(おおがきやすお)

University of Denver卒。同校にてコンピュータサイエンスとビジネスを学ぶ。株式会社シーエーシーを経て,エレクトロニック・サービス・イニシアチブ有限会社を設立。
オープンソース製品は比較的古くから利用し,Linuxは0.9xのころから利用している。オープンソースシステム開発への参加はエレクトロニック・サービス・イニシアチブ設立後から。PHPプロジェクトでは,PostgreSQLモジュールのメンテナンスを担当している。

URLhttp://blog.ohgaki.net/

著書