エグゼクティブサマリ
2011年始めに徳丸がescapeshellcmdの危険性を指摘したが、この問題はmail関数のadditional_parameters経由で攻撃可能であることが2013年末に指摘された。その後2016年末に、PHPMailerの脆弱性CVE-2016-10033として現実のものとなった経緯
- 2011/1/1 徳丸が「PHPのescapeshellcmdの危険性」を書いて、クォート文字がペアになっている場合にエスケープしないという仕様が余計なお世話であり、危険性が生じていることを指摘
- 2011/1/7 大垣さんがブログエントリ「phpのescapeshellcmdの余計なお世話を無くすパッチ」にて修正案を提示
- 2011/10/23 廣川さんが、大垣さんのパッチ案を少し修正してbugs.php.netに提案。修正案は却下され、マニュアルを修正することに
- 2011/11/19 escapeshellcmdのマニュアルに引数インジェクションの危険性が追記された
- 2013/12/23 mail関数のadditional_parametersの危険性について小邨孝明さんがブログ記事にて指摘
- 2014/1/4 mail関数のマニュアルにadditional_parametersの危険性を追記(大垣さんによる)
- 2016/12/25 Dawid GolunskiがPHPMailerの脆弱性CVE-2016-10033を公表
escapeshellcmdはなぜ危険か
経緯のところで説明したように、escapeshellcmdはコマンド文字列全体をエスケープするための関数です。サンプルとして以下のプログラムを考えます。
このプログラムは外部から指定されたメールアドレスに対して空のメールを送信するものです(実用上の意味はありませんが説明を簡単にするためですのでご容赦ください)。…が、一見して分かるようにOSコマンドインジェクション脆弱性があります。例えば、メールアドレスとして下記のメールアドレスを指定します。<?php $mail = $_GET['mail']; $cmd = "/usr/sbin/sendmail -i $mail"; system($cmd);
実行されるコマンドは下記となり、メール送信に続いて、cat /etc/passwd が実行されます。[email protected]; cat /etc/passwd
このような攻撃を防ぐために、escapeshellcmdは下記のように用います。/usr/sbin/sendmail [email protected]; cat /etc/passwd
この場合、実行されるコマンドは下記となり、OSコマンドインジェクションは避けられます。<?php $mail = $_GET['mail']; $cmd = "/usr/sbin/sendmail -i $mail"; $cmd = escapeshellcmd($cmd); system($cmd);
しかし、cat /etc/passwdという余計なオプションがついてしまいますので、$mailをダブルクォートで囲むことも一案です。/usr/sbin/sendmail -i [email protected]\; cat /etc/passwd
実行結果は下記となります。<?php $mail = $_GET['mail']; $cmd = "/usr/sbin/sendmail -i \"$mail\""; $cmd = escapeshellcmd($cmd); system($cmd);
今度は ; cat /etc/passwdも含めてメールアドレスの一部となり、メール送信はエラーになりますが、OSコマンドインジェクションもオプション追加も避けられています。/usr/sbin/sendmail -i "[email protected]\; cat /etc/passwd"
しかし、このような使い方を想定すると、ダブルクォート「"」はエスケープしてはいけないことになり、実際エスケープされていません。このため、escapeshellcmdのマニュアルには以下のように記載されています。
' および " は、対になっていない場合にのみエスケープされますということは、メールアドレスとして下記を指定した場合、
実行されるコマンドは下記となり、パラメータインジェクション攻撃が成立します。-OQueueDirectory=/tmp" "-X/tmp/exploit.php" "[email protected]
この攻撃が成立する理由は、前述のとおり、escapeshellcmdの「' および " は、対になっていない場合にのみエスケープされます」という仕様によるものであり、これはescapeshellcmdのユースケースを考えると不可避であり、仕様の欠陥といえるものです。このため、OSコマンドインジェクション対策には、escapeshellcmdの利用を避け、escapeshellarg関数を用いる必要があります。/usr/sbin/sendmail -i "-OQueueDirectory=/tmp" "-X/tmp/exploit.php" "[email protected]"
CVE-2016-10033の本質は、mail関数とmb_send_mail関数が、OSコマンドインジェクション対策として内部的にescapeshellcmd関数を呼び出しているために、潜在的にパラメータインジェクションに対して脆弱である点をついたものです。
CVE-2016-10033 PoCの巧妙さ
実際のCVE-2016-10033は上記の説明とは少し違います。mail関数は、宛先をメールヘッダから取得する -t オプションが指定されていますが、PHPMailerはSender(エンベロープFrom)の設定するため、sendmailコマンドの-fオプションをmail関数の第5パラメータadditional_parameters経由で指定しています。典型的なsendmail呼び出しは下記となります。ご覧のように-fオプション全体やメールアドレスはダブルクォートで囲まれていません。しかし、これを突破するのは容易ではありません。なぜなら、/usr/sbin/sendmail -t -i [email protected]
- PHPMailerはSenderプロパティに対してRFC準拠のメールアドレスであることを確認している
- sendmail呼び出しの前にescapeshellcmdによるエスケープ処理がなされている
- sendmailコマンドのパラメータチェックを回避する必要がある
このメールアドレスは、RFC5321等を満たしています。escapeshellcmdを通ったあとのsendmailコマンド呼び出しは下記となります。"a \" -OQueueDirectory=/tmp -X/tmp/exploit.php \"a"@example.jp
"は偶数個あるためエスケープされませんが、バックスラッシュ「\」はエスケープされています。この不整合により、/bin/sh経由で呼び出されたsendmailコマンドが受け取るパラータは下記となります。/usr/sbin/sendmail -t -i -f"a \\" -OQueueDirectory=/tmp -X/tmp/exploit.php \\"a"@example.jp
「a \」と「\[email protected]」はメールアドレスが必要なパラメータであり、RFCには違反していますが、sendmailは一旦これらをエラーとせずメール配送を開始するため、攻撃が成立してしまいます。-t -i -fa \ -OQueueDirectory=/tmp -X/tmp/exploit.php \[email protected]
mail関数はどうすればいいか
mail関数の今のマニュアルには、以下のように書かれています。escapeshellcmd() が自動的に適用されるため、 インターネット RFC でメールアドレスとして許可さているいくつかの文字を使用することができません。 mail() はそうした文字を許可しないため、プログラム中でそうした文字の使用が必須である場合、 メール送信の代替手段(フレームワークやライブラリの使用など)が推奨されます。つまり、RFCよりも制限を厳しくしたバリデーションを施さないと危険だということですが、この状態はよろしくないと考えます。現に、PHPMailerだけでなく、Swift Mailerにも同種の指摘(CVE-2016-10074)がなされていますが、PoCが同じなので、mail関数の仕様による問題と考えられます。
PHP: mail - Manual より引用
これを改善するには、additional_parametersとして現在の文字列パラータに加えて、配列を許すようにする案が考えられます。配列としてパラメータを一つずつ指定できれば、escapeshellarg関数により安全にエスケープ処理が行えるからです。
まとめ
2011年はじめにescapeshellcmdの問題点を指摘した後、2016年末にPHPMaierの脆弱性CVE-2016-10033という重大な問題として顕在化した流れを報告しました。PHPは長い歴史の中で安全性を高めており、この記事や、このスライドにまとめたことがありますが、まだ危険な関数は残っています。今回の問題は、escapeshellcmd、mail、mb_send_mail関数の仕様が問題でした。今回の問題を機に、さしあたりmail関数とmb_send_mail関数の仕様が見直されることを期待しています。