先日、お伝えしたとおり、hhvm では php の設定でよく使われる disable_functions が使えません。
Wordpress用のサーバーを少し調べてみたのですが、 wpXとWPblogでは、disable_functionsを使っていますが、GMO WP Cloudでは、使っていません。
どうやって調べたかというと、この問題を知るきっかけとなった画像軽量化プラグイン EWWW Image Optimizer の設定画面に exec() の実行が有効、無効が表示されます。システムへの侵入はしてませんよ。
気付き:GMO WP Cloudの思考
このとき、GMO WP Cloudはどうやってセキュリティを保持しているのか気になりました。サイトには「テーマ及びプラグインは、Wordpress公式の中で、最新のWordPressに対応したものを利用する」と書かれています。なるほど。
つまり、WPの公式サイトから、ダウンロードしてきたプラグインが exec()を使う場合は、禁止を解除するアルゴリズムを打ち込んどけば、大きな問題が発生しないという事に気が付きました。やはり、GMOさんは経験が豊かです。
調査:代替機能の発掘
ということで、hhvmでもこの手法でのりきれるのではないかと模索した結果、出来ました!!やっちゃいました!!それを、実現するための関数が、fb_intercept() と fb_rename_function() です。と言ってもこの関数、まだ、テスト中みたいでhhvm公式のリファレンスには、書かれていません。アップデート情報を見ると3.3.0でその痕跡を確認できます。ちょっと古いかもしれませんが Jared Kipe Software にリファレンスがありました。
テスト中の関数ということで、慎重な取扱と、仕様が変わるリスクを考えながら自己責任で使用してくださいね。
リファレンスの発掘までできればあとは野となれ山となれです。
設定:server.iniに1行追加
fb_intercept と fb_rename_functionを動作させるには /etc/hhvm/server.ini に hhvm.jit_enable_rename_function を設定しなければいけません。下記の1行を追加してください。
1 |
hhvm.jit_enable_rename_function = true |
創作:スクリプトコードをつくる
それでは、 EWWW Image Optimizer に対応した、disable_function もどきを作っていきましょう。
まず、 fb_intercept で exec() を インターセプトして横どる方法です。
1 2 3 4 |
function __exec_fake_func($name, $obj, $args, $data, &$done) { $done = false; } fb_intercept('exec', '__exec_fake_func'); |
exec関数が呼ばれたら、まず、__exec_fake_func()を実行します。$doneは、__exec_fake_func()実行後に、exec()を実行するかしないか決めれます。__exec_fake_func()の関数名は任意で問題ありませんが重複しないような名前にしてあげてください。
exec()の場合、
- $name == ‘exec’
- $obj == null
- $args == 外部プログラムとその引数
- $data == null
- $done == true
が、設定された状態で __exec_fake_func() の引数になります。
例えば、exec(‘ipconfig /all’); が実行された場合 $argsには’ipconfig /all’という文字列が返ってきます。
つまり、$args の最初のトークンが、execで実行したい外部プログラムの名前になるということです。これを、引っ張ってきて分岐させれば実行させたい外部プログラムのみ exec() を通すことが可能です。
次に、EWWW Image Optimizer に必要な外部プログラムをリストアップ。
- nice
- jpegtran
- optipng
- gifsicle
- pngquant
- convert
この5つが exec() を使用してますね。調べ方は、ソース読んだり、fb_intercept で引っ掛けたりします。そして、これらが実行させたい外部プログラムとなります。パッと見た感じ、セキュリティに深刻な影響を与えそうなものは無いですね。
むかし、JPEGの脆弱性というセキュリティ・ホールというのもあったので外部プログラムのアップデートには気を張る必要はありますよ。
それではこれを踏まえて、スクリプトを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function __exec_fake_func(($name, $obj, $args, $data, &$done) { $comm = explode(" ",$args[0]); $bn = basename($comm[0]); switch ($bn) { case 'nice': case 'jpegtran': case 'optipng': case 'gifsicle': case 'pngquant': case 'convert': $done = false; break; default: error_log("-- Exec Alert Warning:".var_export($name, true)); $to = 'xxxxxxxx@xxxxx.xxxxx'; $title = '[exec] Alert Warning'; $message = 'Call the ['.$bn.'] . not found'; $header = 'From: yyyyyyyy@yyyyyy.yyy' . "\r\n"; $header .= 'Return-Path: yyyyyyyy@yyyyyy.yyy'; mail($to, $title, $message, $header); break; } } function __phpinfo_fake_func(($name, $obj, $args, $data, &$done) { echo 'disable phpinfo()'; } fb_intercept('exec', '__exec_fake_func'); fb_intercept('phpinfo', '__phpinfo_fake_func'); |
勢い余って phpinfo も対応しちゃいました。
EWWW Image Optimizer が使われるコマンドだけ $doneをfalseにして、exec()を実行し、それ以外の場合は、エラーログの出力と、メールでのお知らせをします。これに、バックトレースつけれたら最高だなぁ。
でもね、fb_intercept て上書き出来るんだよね。この対応だけじゃぁ、不十分。ということでもう少しお付き合いください。
さて、fb_intercept の上書きってどういうことかというと、
1 |
fb_intercept('', null); |
と、記述されるとそれまでに設定してきた、fb_intercept の内容を全て破棄します。ということは、トロイの木馬的プラグインに、fb_intercept(”, null); を書かれていると、その以降は、exec()が利用できるようになってしまいます。
これでは、元も子もありません。
そこで、fb_rename_function() を使います。これは、関数の名前をあとから変更できる魔法のような関数です。しかも、変更前の関数は消滅します。これを使って fb_intercept() を消し去ります。
実際には消し去ると、fb_intercept() が呼び出された瞬間に処理が止まってしまうので消せないのですが、そこをうまく調理してくれるのが fb_rename_function() です。
1 2 3 4 5 |
function fb_fake_intercept( $str ) { error_log('Call fb_intercept'); } fb_rename_function('fb_intercept', 'fb_escape_intercept'); fb_rename_function('fb_fake_intercept', 'fb_intercept'); |
を、先のソースに追記します。
そうすると、本来の fb_intercept は fb_escape_intercept という名前で呼びださないといけなくなります。そして、仮に邪悪なプラグインが fb_intercept を呼び出しても、fb_fake_intercept が動くので、エラーログに痕跡を残すだけとなります。
これを応用して、他の怪しい関数も fb_intercept しちゃいましょう。
作ったスクリプトは、server.ini で auto_prepend_file として読みこむか、サイト閲覧時に必ず呼び出されるようなphpに記述するすればOKです。Wordpressだと プラグインでフックさせたり、wp-config.php に書くのも良いかもしれません。
おわり
コメント