
CVE-2019-11043 用エクスプロイト
これは、php-fpmのバグ(CVE-2019-11043)に対するエクスプロイトです。特定のnginx + php-fpm構成では、外部からこのバグをトリガーすることが可能です。つまり、脆弱な設定(下記参照)がある場合、Webユーザーがコード実行を達成する可能性があります。
解説記事を書くのは面倒でしたが、Orange Tsaiが彼のブログで完璧な分析を公開しています。彼に敬意を表します。
また、ZeroNights 2019での私のスライドはこちらから入手可能です。
Webサーバーがnginx + php-fpmを実行し、nginxが以下のような設定を持つ場合:
location ~ [^/]\.php(/|$) {
...
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php:9000;
...
}
かつ、スクリプトの存在確認(try_filesなど)が欠けている場合、このエクスプロイトでハッキングできる可能性があります。
location ~ [^/]\.php(/|$) が php-fpm に転送される必要があります(正規表現はより厳密かもしれません。#1を参照)。fastcgi_param PATH_INFO $fastcgi_path_info; という文による PATH_INFO 変数の代入が必要です。また、SCRIPT_FILENAME は fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; を使用して設定されている必要があります($document_root の代わりに定数パスが使われている場合もあります)。当初はこれらが常に fastcgi_params ファイルに存在すると思っていましたが、そうではありませんでした。PATH_INFO を空の値に設定する方法が必要です。このエクスプロイトは、fastcgi_split_path_info ディレクティブが存在し、^ で始まり $ で終わる正規表現を含むことを前提としているため、改行文字で正規表現を壊そうとします。PATH_INFO が REQUEST_URI の後に設定されていることを前提としています。try_files $uri =404 や if (-f $uri) のようなファイル存在確認が存在してはいけません。NginxがFastCGI転送前に存在しないスクリプトへのリクエストを破棄すると、リクエストがphp-fpmに届かなくなります。これを追加することは、最も簡単なパッチ方法でもあります。ずいぶん昔、php-fpmはスクリプトの拡張子を制限していなかったため、/avatar.png/some-fake-shit.php のようなものが avatar.png をPHPスクリプトとして実行できました。この問題は2010年頃に修正されました。
現在のものはファイルのアップロードを必要とせず、最新バージョンでも(修正が適用されるまで)動作し、そして最も重要なことに、エクスプロイトがはるかにクールです。
以下のようにインストールします:
go get github.com/neex/phuip-fpizdam
奇妙なコンパイルエラーが発生した場合は、go 1.13以上を使用していることを確認してください。プログラムは phuip-fpizdam [url] で実行します($GOPATH/bin が $PATH 内にあると仮定します。そうでない場合はバイナリへのフルパスを指定してください)。正常な出力は次のようになります:
2019/10/01 02:46:15 Base status code is 200
2019/10/01 02:46:15 Status code 500 for qsl=1745, adding as a candidate
2019/10/01 02:46:15 The target is probably vulnerable. Possible QSLs: [1735 1740 1745]
2019/10/01 02:46:16 Attack params found: --qsl 1735 --pisos 126 --skip-detect
2019/10/01 02:46:16 Trying to set "session.auto_start=0"...
2019/10/01 02:46:16 Detect() returned attack params: --qsl 1735 --pisos 126 --skip-detect <-- これを覚えておいてください
2019/10/01 02:46:16 Performing attack using php.ini settings...
2019/10/01 02:46:40 Success! Was able to execute a command by appending "?a=/bin/sh+-c+'which+which'&" to URLs
2019/10/01 02:46:40 Trying to cleanup /tmp/a...
2019/10/01 02:46:40 Done!
この後、すべてのPHPスクリプトに ?a=<コマンド> を追加して実行できるようになります(複数回のリトライが必要な場合があります)。
もしくは、dockerイメージを使用してエクスプロイトを実行することもできます:
docker run --rm ypereirareis/cve-2019-11043 [url]
ローカルのDockerで問題を再現したりエクスプロイトを試したりするには、次の手順を実行します:
reproducer ディレクトリに移動します。docker build -t reproduce-cve-2019-11043 . でDockerイメージを作成します。内部でPHPリポジトリをクローンしソースからビルドするため、時間がかかります。ただし、エクスプロイトをデバッグしたい場合はこの方法が簡単です。ビルドされるリビジョンは修正が行われる直前のものです。docker run --rm -ti -p 8080:80 reproduce-cve-2019-11043 でDockerを実行します。phuip-fpizdam http://127.0.0.1:8080/script.php でエクスプロイトを実行します。?a= を追加することでコマンドを実行できるようになります:http://127.0.0.1:8080/script.php?a=id。感染しているphp-fpmワーカーは一部だけなので、複数回試してください。ローカルのLXDで問題を再現したりエクスプロイトを試したりするには、次の手順を実行します:
vulnerable と attacker の2つのシステムコンテナを作成します。両方のコンテナに ubuntu:18.04 コンテナイメージを使用できます。vulnerable コンテナに nginx と php-fpm をインストールします。サーバーブロックをこの設定のように構成します。空のファイル /var/www/html/index.php を作成します。attacker コンテナにGo言語をインストールし(sudo snap install go --classic)、このリポジトリをクローンして、リポジトリのディレクトリで go build を実行します。./phuip-fpizdam http://vulnerable.lxd/index.php。すべてのphp-fpmワーカーに感染させるために、複数回実行してみてください。詳細な手順については、Testing CVE-2019-11043 (php-fpm security vulnerability) with LXD system containersを参照してください。
php-fpmのバッファアンダーフローはPHPバージョン5にも存在します。ただし、このエクスプロイトはFastCGI変数を格納するために使用される最適化、_fcgi_data_segを利用しています。この最適化はPHP 7にのみ存在するため、この特定のエクスプロイトはPHP 7でのみ動作します。PHP 5で動作する別のエクスプロイト手法が存在する可能性があります。
元の異常は、Real World CTF中にd90pwnによって発見されました。根本原因の発見とphp.iniオプションを設定する方法は私(Emil Lerner)によるものです。最終的なphp.iniオプションセットはbechedによって発見されました。
このエクスプロイトはMITライセンスの条件に基づいて配布されています。
このエクスプロイトでいかなる損害も与えないでください。しかし、もしこのツールで何かを本当にハッキングしたなら、私は嬉しく思います。