2015年12月18日金曜日

Joomla!の「ゼロデイコード実行脆弱性」はPHPの既知の脆弱性が原因

Joomla!にコード実行脆弱性があり、パッチ公開前から攻撃が観測されていたと話題になっています。
パッチ公開の前に攻撃が始まる状態を「ゼロデイ脆弱性」と言いますが、それでは、この脆弱性のメカニズムはどんなものだろうかと思い、調べてみました。
結論から言えば、この問題はJoomla!側に重大な脆弱性はなく、PHPの既知の脆弱性(CVE-2015-6835)が原因でしたので報告します。

exploitを調べてみる

既にこの問題のexploitは公開されていますが、悪い子が真似するといけないのでURL等は割愛します。以下のページでは攻撃の原理が説明されています。
ざっくり言うと、以下の様な攻撃です。
  1. Joomla! がUser-Agentをセッション変数に保存するので、セッション形式のデータ(文字列)をUser-Agent経由でセットする
  2. その際、MySQLのデフォルトでは、UTF-8の4バイト形式があると、それ以降をトランケートする仕様を悪用して、攻撃に不要なデータを切り詰める
  3. 何ということでしょう! 切り詰めをすると、文字列がオブジェクトに化けるではありませんか
  4. 上記によりJoomlaのJDatabaseDriverMysqli、SimplePie等のクラスのオブジェクトをインジェクションして、デストラクタにより任意コードを起動
全体的には、典型的なオブジェクトインジェションによるコード実行です。なぜデストラクタにより任意コードが実行できるかについては、以下の記事を参照下さい。
問題は、上記の 3 です。なぜ、文字列を切り詰めると、文字列型のデータがオブジェクトに化けるのでしょうか。前記の記事にはその辺の説明はなかったので、自分で調べてみました。

シンプルな再現コード

以下は、セッション変数にログインユーザ名とUser-Agentのみをセッション変数にセットして、その際のセッション形式の文字列を表示するだけの簡単なプログラムです。
<?php
// インジェクションするクラス(攻撃対象の既存クラス)
class Class1 {
  private $private = 'prv';
  function __destruct() {
    echo "Class1::__destruct()\n";
  }
}

session_start();

$id = 'ockeghem'; // ログインユーザ名
// $browser はUser-Agentであり外部からコントロールできる
$browser = 'Mozilla/5.0';

$_SESSION['_default']['id'] = $id;
$_SESSION['_default']['browser'] = $browser;

echo str_replace("\0", '%00', session_encode()) . "\n";
出力は以下となります。
_default|a:2:{s:2:"id";s:8:"ockeghem";s:7:"browser";s:11:"Mozilla/5.0";}
セッション変数のシリアライズ形式は、以下のような形です。
  • _default   セッション変数のキー
  • a:2:{...}  要素数2の配列
  • s:2:"id"   文字列長の2の文字列「id」
一方、外部からは操作できませんが、browserのところにオブジェクトをセットした場合の形式は下記のとおりです。ヌル文字を含むため、パーセントエンコードした形で表示しています(bloggerの制限により%記号は全角にしていますが実際は半角です)。
_default|a:2:{s:2:"id";s:8:"ockeghem";s:7:"browser";O:6:"Class1":1:{s:15:"%00Class1%00private";s:3:"prv";}}
これを念頭におきつつ、外部からUser-Agentとして以下を設定してみます(パーセントエンコード形式)。■は、実際には、UTF-8の4バイト形式の文字をセットします。
_aa|O:6:"Class1":1:{s:15:"%00Class1%00private";s:3:"prv";}■
セッション変数のシリアライズ結果は下記となります。
_default|a:2:{s:2:"id";s:8:"ockeghem";s:7:"browser";s:57:"_aa|O:6:"Class1":1:{s:15:"%00Class1%00private";s:3:"prv";}■";}
しかし、これはあくまで文字列です。それは、s:57という型指定がある以上、外部からは変更できないはずです。
しかし、Joomla!のデフォルトのセッションストレージはMySQLであり、文字エンコーディングはutf8_general_ciとなっています。この場合、MySQLは、UTF-8の4バイト形式の文字があると、その文字を含めてそれ以降が切り詰められる、というすごい仕様があります。

このため、セッションをいったんMySQLに保存して、次のページでそのセッションデータを読みだすと、以下のように■以降がカットされた状態となります。
_default|a:2:{s:2:"id";s:8:"ockeghem";s:7:"browser";s:57:"_aa|O:6:"Class1":1:{s:15:"%00Class1%00private";s:3:"prv";}
このセッションデータをダンプすると、何ということでしょう! 文字列だった部分がオブジェクトになっているではありませんか。そして、オブジェクトが生成されているので、このデストラクタも実行されます。
array(2) {         ← ダンプ
  ["_default"]=>
  NULL
  ["57:"_aa"]=>
  object(Class1)#1 (1) {
    ["private":"Class1":private]=>
    string(3) "prv"
  }
}
Class1::__destruct()    ← デストラクタの実行
上記から、以下のことが言えます。
  • これは Joomla! の脆弱性ではない

この問題の原因は何か

phpallを使ってこの問題を確認すると、PHPの以下のバージョンで起こることが分かります
  • PHP-5.0.3~PHP-5.0.5
  • PHP-5.1.x ~ PHP-5.3.x 全て
  • PHP-5.4.0~PHP-5.4.44
  • PHP-5.5.0~PHP-5.5.28
  • PHP-5.6.0~PHP-5.6.12
すなわち、以下のバージョンで修正されています。
  • PHP-5.4.45
  • PHP-5.5.29
  • PHP-5.6.13
  • PHP-7.0.0
これらで修正されている脆弱性はなんだろうと思ってリリースノートを見ると、以下が該当するようですね。
  • Fixed bug #70219 (Use after free vulnerability in session deserializer). (CVE-2015-6835)
セッション・デシリアライザのUse after free脆弱性ということで、挙動から見てもビンゴでしょう。

Linuxディストリビューションではどうか?

PHP本家の対応は上記のとおりですが、RHEL/CentOS等のLinuxディストリビューションから、パッケージとして提供されているPHPのパッチの対応状況はどうでしょうか?
  • CentOS 5,6,7ともパッチ提供なし(参考
  • Ubuntu 12.04、14.04ともパッチ提供済み(参考
  • Debian/GNU Linux Debian6はパッチ未提供、7以降は提供済み
  • Fedora Fedora21以降でパッチ提供済み(20はサポート終了でパッチ提供なし)
Red Hat社の脆弱性トリアージ(緊急度判断)は通常妥当だと思っているのですが、この件に関しては外れてしまったようです。ただし、シリアライズされたセッションデータが切り詰められるなんて状況はちょっと予想しがたいので、悪いのはMySQLの仕様だ!、と思わなくもありません。

影響を受けるソフトウェア

PHPのこの脆弱性の影響は、Joomla!に限られるものではありません。セッションストレージとしてMySQLを使うことは、一般的によく行われているからです。

このため、影響の有無判定はともかく、PHPの最新版を使うことを推奨します。

RHEL/CentOSについてはパッチが提供されていないので、個別の対策をとるしかないでしょう。例えば、以下の様な対応が考えられます。
  • アプリケーション側で対応したバージョンを導入する(例:Joomla! 3.4.6)
  • MySQL 5.5.3 以降で使える utf8mb4 エンコーディングを使用する
  • セッションストレージとしてMySQLを避ける
  • バリデーションによりUTF-8の4バイト形式をエラーにする
緩和策としては、WAF(Web Application Firewall)の導入があります。

まとめ

Joomla!のリモートコード実行の問題を分析しました。これまで説明したように、この問題の直接の原因は、Joomla!側にはなく、PHPの既知の脆弱性(CVE-2015-6835)が原因です。私も「Joomla!にゼロデイ脆弱性」とツイートしてしまいましたが、Joomla!の問題ではないと訂正いたします。

この問題は、
  • PHPの通常は顕在化しない脆弱性CVE-2015-6835が
  • MySQLのUTF-8 4バイト形式以降を切り詰めるという仕様のため顕在化した
ことが問題と考えます。とくに、MySQLの上記挙動は、こちらでも書いたように、長いデータを黙って切り詰めると思わぬバグや脆弱性の原因になり得るという例だと言えるでしょう。



【HASHコンサルティング広告】
HASHコンサルティングが販売するWAF(SiteGuard、SiteGuard Lite)は、このJoomla!のコード実行の問題に対応したシグネチャを提供済みです。お問い合わせはこちら
HASHコンサルティング株式会社は、セキュリティエンジニアを募集しています。
興味のある方は、twitterfacebookのメッセージ、あるいは問い合わせページからお問い合わせください。


2 件のコメント:

  1. ワークアラウンドをもうひとつ。
    MySQLのsql_modeにSTRICT_TRANS_TABLESまたはSTRICT_ALL_TABLESを指定すると切り詰めは発生しなくなります(エラーになるようになります)

    https://gist.github.com/yoku0825/19f6fe20ecadb1928346

    返信削除
  2. Twitter などでも指摘されておりますが、文字列型のカラムにバイナリを入れるという joolma! 側の設計にそもそもの問題があり、LONGBLOB などにデータを格納すべきだったと思います。
    文字列型のカラムにバイナリを入れるという設計に問題があるのでこの脆弱性は、やはり joolma! の側の脆弱性といって差し支えないように思います。

    返信削除

フォロワー