今回の脆弱性
追記
2016/12/28 14:15
Postfixを使っていて、sendmailコマンドの代わりにPostfixのsendmailコマンドを使っている場合は、Postfixのsendmailコマンドが -X オプションを無視するようですので大きな影響を受けないと思います。ただ、別のオプションで違う脆弱性が発生する可能性もあるので、PHPMailerはアップデートしたほうが良いですね。
2016/12/28 15:35
PHPMailer5.2.18の修正を回避する新しい攻撃が見つかり、 CVE-2016-10045として登録されその攻撃方法も公開されてます。こちらの修正パッチはまだ存在しないみたいです。
この攻撃は、PHPMailer5.2.18で入った escapeshellarg()を回避するようになってます。こんな感じで。
暫定的にパッチ入れるなら、メールアドレスのバリデーションをPHP標準関数のfilter_varを使う方向にするのが手軽で良さそう。PHP5.2以上に限りますが。
//public static function validateAddress($address, $patternselect = null) この行を下記のように。
public static function validateAddress($address, $patternselect = 'php')
脆弱性の概要
PHPMailer5.2.18より下のバージョンで発生します。
フォームからメールアドレスを受け付けて自前バリデーションせずにPHPMailerのFromにセットしている場合に発生する可能性。
任意のPHPコードをサーバに設置できるため、外部からWebシェルなどが設置されるなど、影響は大きいです。
影響が大きいため、PHPMailerを使っているCMSなど(WordPress, Drupal, 1CRM, SugarCRM, Yii,Joomla!)も含めてバージョンアップが必要です。PHPMailerはメール送信ライブラリのため、知らない間にプラグインなどで利用されている可能性もあります。
WordPressの場合はwp-includes/class-phpmailer.phpですかね。
2016年12月28日の時点で、このチケットで議論されているようです。
すぐにアップデートできない人は、
$params = sprintf('-f%s', $this->Sender);となっている箇所を、
$params = sprintf('-f%s', escapeshellarg($this->Sender));にしておくと良いかも。(自己責任で)
影響範囲
PHPMailer5.2.18より下のバージョンで、メール送信にSendmailを使っている場合、下記の条件になります。
- PCREが入っている場合
- すべてのPHPバージョンで影響
- PCREが入っていない場合
- PHP5.2より下のバージョンで影響
- PHP5.2より下のバージョンで影響
PoCでは、以下のようにPCREが入ってないことが条件になってますが、それはこのPoCの条件であって他のパターンではPCREが入ってても抜けてしまいます。
- PHPMailer < 5.2.18
- Compile PHP without PCRE.
- PHP version must be inferior to 5.2.0.
例えばこんなコードで検証すればPCREが入ったPHP5.2以上の環境でもメールアドレスのバリデーションは通ってしまいます。
<?php
require 'PHPMailerAutoload.php';
//$address = 'ichikaway@gmail.com';
$address = '"attacker\" -oQ/tmp/ -X/var/www/cache/phpcode.php some"@email.com';
var_dump(PHPMailer::validateAddress($address)); // true
悲しいことに、PCREが入っているほうがPHPの対象範囲が広くなってしまいます・・・
PCREが入っておらず、PHP5.2以上の場合はPHPのfilter_var()でメールアドレスチェックが行われるため救われます。PCREの正規表現よりもfiter_var()を優先すれば良いのに・・・
コードはこのあたりをみればバリデーションの適用条件がわかると思います。
https://github.com/PHPMailer/PHPMailer/blob/master/class.phpmailer.php#L1068
最新版のパッチ
パッチはこれです。
この箇所で、escapeshellarg()を使って引数として利用される文字列をエスケープしています。