Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

2015年7月24日金曜日

セキュアなWEBサイト運用のためのワークショップ(東京)にて講演します

キヤノンITソリューションズ株式会社主催のセミナー「セキュアなWEBサイト運用のためのワークショップ~ハッカーが攻撃をあきらめるWEBサイトとは?~」にて講演します。

日時:2015年7月29日(水)13:30~17:00 受付は13:00より
場所:フクラシア品川クリスタルスクエア 3階 会議室G (東京都港区 地図
費用:無料(申し込みはこちら
講演タイトル:やぶられにくいWEBサイトの作り方  ~被害事例から学ぶ攻撃手法とその対策~

企業セミナーとしては長めの80分という枠をいただいていますので、具体的な攻撃手法をたっぷりお見せしたいと思います。「被害事例から学ぶ…」というタイトルですので、具体的な事例の手法(推測含む)の攻撃手法のデモをお見せします。
  • 情報通信研究機構(NICT)ウェブサイトに対する侵入事件(Joomlaの脆弱性)
  • メガネ通販サイトに対する侵入事件(Struts2の脆弱性によりサイト改ざん、入力内容窃取)
  • EC-CUBEカスタマイズ部分のSQLインジェクションによる個人情報窃取
  • パスワードリスト攻撃による不正ログイン
  • なりすまし犯行予告をCSRF攻撃で
  • なりすまし犯行予告をXSS攻撃で
  • なりすまし犯行予告をクリックジャッキング攻撃で
  • その他
大阪でもほぼ同じ内容でやりましたが、今回は少し趣向を変えて、「なりすまし犯行予告」をCSRF、XSS、クリックジャッキングの3パターンで実演いたします。これら攻撃手法の *具体的な* 悪用方法やその違いが理解しやすいかと思います。

それでは、よろしくお願いいたします。

2015年7月15日水曜日

PHPのunserialize関数に外部由来の値を処理させると脆弱性の原因になる

既にいくつかの記事で指摘がありますが、PHPのunserialize関数に外部由来の値を処理させると脆弱性の原因になります。 しかし、ブログ記事等を見ていると、外部由来の値をunserialize関数に処理させているケースが多くあります。

ユースケースの一例としては、「複数の値をクッキーにセットする方法」として用いる場合です。 PHP クッキーに複数の値を一括登録する方法という記事では、以下の方法で複数の値をクッキーにセットしています。
$status = array(
 "height" => 167,
 "weight" => 50,
 "sight" => 1.2
);
setcookie("status", serialize($status));
クッキーの受け取り側は以下のコードです。
print_r(unserialize($_COOKIE['status']));
出力結果は以下となります。
Array
(
[height] => 167
[weight] => 50
[sight] => 1.2
)
このようなunserialize関数の使い方は危険なのですが、上記に示したコードの範囲では脆弱性とまでは言えません。そこで、脆弱な例(getcookie.php)を下記に示します。
<?php
  require_once 'Logger.php';  // ログ出力クラス

  if (empty($_COOKIE['status']))
    die('クッキーが空です');
  $status = unserialize($_COOKIE['status']); // デシリアライズ

  // 以下バリデーション
  if (! is_array($status))
    die('statusは配列が必要です');
  if (! isset($status['height']))
    die('heightがセットされていません');
  if (! isset($status['weight']))
    die('weightがセットされていません');
  if (! isset($status['sight']))
    die('sightがセットされていません');

  // 以下表示
  echo 'height : ' . htmlspecialchars($status['height']) . '<br>';
  echo 'weight : ' . htmlspecialchars($status['weight']) . '<br>';
  echo 'sight : ' . htmlspecialchars($status['sight']) . '<br>';
ここでログ出力クラスLogger.phpの中身は以下となっています。
<?php
class Logger {
  const LOGDIR = '/tmp/';  // ログ出力ディレクトリ
  private $filename = '';  // ログファイル名
  private $log = '';       // ログバッファ

  public function __construct($filename) {  // コンストラクタ…ファイル名を指定
    if (! preg_match('/\A[a-z0-9\.]+\z/i', $filename)) { // ファイル名のバリデーション
      throw new Exception('Logger: ファイル名は英数字とドットで指定して下さい');
    }
    $this->filename = $filename; // ファイル名
    $this->log = '';             // ログバッファ
  }

  public function __destruct() { // デストラクタではバッファの中身をファイルに書き出し
    $path = self::LOGDIR . $this->filename;  // ファイル名の組み立て
    $fp = fopen($path, 'a');
    if ($fp === false) {
      die('Logger: ファイルがオープンできません' . htmlspecialchars($path));
    }
    if (! flock($fp, LOCK_EX)) {   // 排他ロックする
      die('Logger: ファイルのロックに失敗しました');
    }
    fwrite($fp, $this->log); // ログの書き出し
    fflush($fp);             // フラッシュしてからロック解除
    flock($fp, LOCK_UN);
    fclose($fp);
  }

  public function add($log) {  // ログ出力
    $this->log .= $log;        // バッファに追加するだけ
  }
}
ご覧のようにLoggerクラスはログ出力を目的としたもので、addメソッドではログをオブジェクト内のバッファに貯めこんでいき、アプリケーションの終了時にデストラクタによりログの中身をファイルに書き出すというものです。

脆弱なサンプル getcookie.php はLogger.phpをインクルードしているものの実際には呼び出しておらず、とくに悪いことはできないように見えますが、現実には、攻撃者はLoggerクラスを悪用する事が可能です。

ここで、攻撃者が使用する攻撃用のスクリプトを示します。これは攻撃者が手元の環境で動かすスクリプトです。
<?php
  require 'Logger.php';  // ファイル名のバリデーションは無効にしておく
  $x = new Logger('../../../var/www/html/evil.php');
  $x->add("<?php phpinfo(); ?>\n");
  setcookie('status', serialize($x));
これが生成するCookieは以下のとおりです。実際にはCookieの値はパーセントエンコードされています。また、下記で[NUL]はヌルバイトを示します。
Set-Cookie: status=O:6:"Logger":2:{s:16:"[NUL]Logger[NUL]filename";s:30:"../../../var/www/html/evil.php";s:11:"[NUL]Logger[NUL]log";s:20:"<?php phpinfo(); ?>
攻撃者はこのクッキーを攻撃対象のサイトで有効になるように自分のブラウザにセットします。この状態で先のgetcookie.phpにアクセスします。問題になる箇所は以下の3行です。
$status = unserialize($_COOKIE['status']); // Loggerオブジェクトがデシリアライズされる
if (! is_array($status))           // 配列ではないので
  die('statusは配列が必要です');   // 終了する
あれあれ、単に「statusは配列が必要です」というエラーメッセージを表示して止まるだけ…ではありません。Loggerオブジェクトが生成されているのでこの後Loggerクラスのデストラクタが動きます。

ここでファイル名は /tmp/../../../var/www/html/evil.php が指定されているので、ディレクトリトラバーサルと同じ原理で /var/www/html/evil.php がオープンされ、バッファの内容 <php phpinfo(); ?>が書き込まれます。
後は、攻撃者は攻撃対象のサイトにて evil.php を閲覧することにより、任意のPHPスクリプトが実行できることになります。

いったんまとめ

攻撃が成立する条件は下記となります。
  • PHPのunserialize関数に与える引数を外部からコントロールできる
  • アプリケーションにてクラスを定義しており、デストラクタが攻撃に悪用できる
  • ドキュメントルート下のディレクトリにPHPスクリプトから書き込みができる
ただし、以下の記事にあるように、攻撃に使える経路としてはデストラクタだけではありません。
また、この問題の有名な脆弱性がCakePHPにありました。以下の記事が参考になります。

何が問題か

攻撃者がunserialize関数に任意の文字列を処理させることが出来る場合、攻撃者は自分の都合の良いクラスのオブジェクトを攻撃対象アプリケーション内で生成させることができます。このクラスは、アプリケーション側で元々定義されているものに限られるため、unserializeがあると直ちに危険であるとは限りませんが、大規模なアプリケーションの場合、危険性を見極めるのはかなり困難であろうと思います。

根本的な解決策

攻撃者が任意のクラスのオブジェクトを自由に生成できる状況は、直接の危険性がないとしても、好ましくない状況と言えます。このため、根本的解決策として以下を推奨します。
  • 外部からコントロールできる値をunserialize関数に処理させない
同じ意味のことをセキュアコーディングの用語を用いると下記のようになります。
  • 信頼境界を超えて渡されるデータをデシリアライズしてはならない
そして、このような文脈では、「信頼できないデータを検証する」とか「信頼できないデータは無害解する」というような表現を見かけますが、このデシリアライズ問題については検証も無害化も困難です。検証が複雑すぎるからです。

このため、serialize/unserializeの代わりに、implode/explodeやjson_encode/json_decodeを使うとよいでしょう。どうしてもserialize/unserializeを使いたい場合は、セッション変数等安全な方法でシリアライズされた値をやりとりします。
  • serialize/unserializeの代わりに、implode/explodeやjson_encode/json_decodeを用いる

保険的対策

Loggerクラスのデストラクタの冒頭は以下のファイル名組み立てですが、局所的にみるとディレクトリトラバーサル脆弱性があります。
$path = self::LOGDIR . $this->filename;  // ファイル名の組み立て
通常のケースでは、コンストラクタでフアイル名をバリデーションしているので、ディレクトリトラバーサル攻撃はできないように見えますが、デシリアライズ等の経路でオブジェクトが生成された場合、コンストラクタは通らないので、不正なファイル名が使われる可能性があります。このため、以下のように$this->filenameにbasename関数を通しておくと安全です。
$path = self::LOGDIR . basename($this->filename);  // 安全なファイル名組み立て
上記は、unserializeによるオブジェクト汚染に対しては保険的な対策ですが、ディレクトリトラバーサル脆弱性に対して根本的解決策となります。
上記のように、一見大丈夫と思える箇所であっても、脆弱性の発生原因箇所に対して局所的に脆弱性を解消しておくことで、アプリケーション全体の安全性を高めることができます。

参考: もう入力値検証はセキュリティ対策として *あてにしない* ようにしよう

また、ドキュメントルート下のディレクトリに対して、PHPスクリプトから書き込みができないように権限を制限することも保険的な対策になります。

まとめ

PHPのunserialize関数の危険性について説明しました。ここで紹介した攻撃方法は、オブジェクトが生成されてしまうとどうしてもデストラクタを通ることになるため、オブジェクトが生成されてからバリデーション等をおこなっても手遅れです。かといって、シリアライズされた状態のままバリデーションを行うことも困難です。このため、外部由来のデータについてはunserialize関数を用いないことが現実的な対策となります。


【HASHコンサルティング広告】
HASHコンサルティング株式会社は、セキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。


フォロワー

ブログ アーカイブ