第32回 PHPセキュリティ月間
今回もMOPS関連の話題です。MOPSではPHP関連のセキュリティ製品やセキュリティ知識の論文を募集し、
- MOPS Submission 07: Our Dynamic PHP - Obvious and not so obvious PHP code injection and evaluation
- http://
www. php-security. org/ 2010/ 05/ 20/ mops-submission-07-our-dynamic-php/ index. html
なぜPHPコード実行が起きるのか?
PHPコード実行が起きる原因は不適切なプログラムが原因であることがほとんどです。不適切なプログラムが作成されてしまう原因のほとんどはプログラマの知識不足です。
今回の記事は、
PHPにおけるコード実行
PHPは動的な言語であるため、
明白なケース ─ eval関数
最も明白なケースはeval関数のパラメータへのコード挿入です。
<?php
eval("echo $foobar;");
?>
このコードの場合、
''; system('ls')
であれば、
echo ''; system('ls');
が実行されます。
<?php
eval('echo $foobar;');
?>
この場合、
<?php
echo $foobar;
?>
を実行した場合と変わりありません。
eval関数を実行した場合に気を付けなければならないことは、
明白なケース ─ スクリプトのインクルード
PHPはinlcude/
外部スクリプトの読み込みの問題は2つの種類に分類できます。
- ローカルファイルの読み込み
(LFI) - リモードファイルの読み込み
(RFI)
この種類の攻撃を防ぐ最良の対策は
Gerkis氏はファイルの読み込みに定数を利用することはよいアプローチだとしています。
<?php
define('APP_PATH', '/var/www/htdocs/');
require_once(APP_PATH . 'lib.php');
?>
そして、
<?php
$to_include = $_GET['file'];
require_once($to_include . '.html');
?>
これは典型的なファイルインクルード脆弱性です。文書の中ではDATA URIを利用した攻撃が紹介されています。上記のような脆弱なコードに対して
http://www.example.com/index.php?file=data:text/plain,<?php phpinfo();?>%00
等とするとPHPのコードが実行できてしまいます。DATA URIを利用した攻撃は脆弱なHTTPリダイレクトを利用したJavaScriptインジェクションの例が有名です。
Location: data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5jb29raWUpPC9zY3JpcHQ+
などとしてJavaScriptインジェクションが可能でした。この攻撃に脆弱なWebアプリケーションがあまりに多いため、
これと同様の攻撃がPHPのinclude文に対して行えます。include文に対するDATA URIを利用した攻撃は非常に危険です。従来、
Perl互換正規表現
正規表現のコールバックを利用したコード実行は時折、
PCRE
<?php
$var = '<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)<\/tag>/e", 'addslashes(\\1)', $var);
?>
これを実行するとphpinfo関数が実行されてしまいます。これは<tag>(.*?)<\/tag>にマッチする文字列がPHPスクリプトとして評価されてしまうために起きる現象です。
正規表現
<tag>(.*)</tag>
が評価する文字列として
<tag>phpinfo()</tag>
を渡すと、
phpinfo()
がバックリファレンスの1番目にマッチした文字列になります。その結果、
addslasshes(phpinfo())
が生成され、
addslasshes('phpinfo()')
と処理されるよう
<?php
$var = '<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)<\/tag>/e", "addslashes('\\1')", $var);
?>
と書くべきです。このコードを実行した場合はphpinfo()は文字列として扱われ関数呼び出しは発生しません。
ここで勘の良い読者は
<?php
$var = "<tag>');phpinfo() //</tag>'";
preg_replace("/<tag>(.*?)<\/tag>/e", "print('\\1')", $var);
?>
これを実行すると
print('');phpinfo() //'
の文字列が生成されevalで評価されればphpinfo関数が実行されるはずですが、
');phpinfo() //
何故、
if (backref < count) {
/* Find the corresponding string match and substitute it
in instead of the backref */
match = subject + offsets[backref<<1];
match_len = offsets[(backref<<1)+1] - offsets[backref<<1];
if (match_len) {
esc_match = php_addslashes_ex(match, match_len, &esc_match_len, 0, 1 TSRMLS_CC);
} else {
esc_match = match;
esc_match_len = 0;
}
} else {
マッチした文字列に対してaddslashesの内部関数であるphp_
print('');phpinfo() //')
とはならず、
print('\');phpinfo() //')
が評価されるのでphpinfo関数は実行されません。このコード実行は古くから知られているので知っている方も多いと思います。そもそも文字列を渡して処理した後の文字列を返すコールバック関数を定義する仕様であるため、
もう少し現実的なコードは以下のようなコードでしょう。マッチした文字列をhtmlentities関数でHTMLエスケープしています。
<?php
$var = '<tag><script>alert(1)</tag><script>alert(2)</script></tag>';
echo preg_replace("/<tag>(.*?)<\/tag>/e", "htmlentities('\\1', ENT_QUOTES, 'utf-8')", $var);
?>
<script>alert(1)<script>alert(2)</script></tag>
正規表現に "?" がついており、
eモディファイアが記載されていない場合でもコードが実行が可能な場合もあります。次のコードでは正規表現中の文字列に変数が利用されています。この変数に細工するとコード実行が可能になります。
<?php
$regexp = "<\/tag>/e\0";
$var = '<tag>phpinfo()</tag>';
preg_replace("/<tag>(.*?)$regexp<\/tag>/", '\\1', $var);
?>
preg_
$regexpを
$regexp = "<\/tag>/e\0";
と設定できれば、
preg_replace("/<tag>(.*?)$regexp<\/tag>/", '\\1', $var);
の正規表現
"/<tag>(.*?)$regexp<\/tag>/"
は$regexpの値で置き換えられ、
"/<tag>(.*?) <\/tag>/e\0<\/tag>/"
となり、
"/<tag>(.*?) <\/tag>/e"
と同じになります。このため、
元の文書では次のコードがPoCとして書かれています。
<?php
$regexp = $_GET['re'];
$var = '<tag>phpinfo()</tag>';
preg_replace ("/<tag>(.*?)$regexp<\/tag>/", '\\1', $var);
?>
攻撃URLとして
http://www.example.com/index.php?re=<\/tag>/e%00
が紹介されています。magic_
Gerkis氏は対策としてできる限り、
まとめ
Gerkis氏の論文をすべては紹介しきれていませんが、
次回もGerkis氏の論文の続きを紹介します。