安全なPHPアプリケーションの作り方2016

Hiroshi Tokumaru
Hiroshi TokumaruSecurity Engineer at EG Secure Solutions Inc.
安全なPHPアプリケーションの作り方2016
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月)
– 技術士(情報工学部門)
Copyright © 2016 HASH Consulting Corp. 2
最近のPHP関連の脆弱性の話題
Copyright © 2016 HASH Consulting Corp. 3
Joomla! の権限昇格脆弱性
CVE-2016-8869、CVE-2016-8870
Copyright © 2016 HASH Consulting Corp. 4
CVE-2016-8869等はどのような問題か?
• どのような問題か?
– ユーザー登録時に、管理者権限を設定されてしまう(CVE-2016-8869)
– ユーザー登録を許可していない設定でもユーザー登録ができてしまう(CVE-2016-8870)
• なにが原因だったか?
– Joomla!内にユーザー登録のメソッドが2つ存在した
• UsersControllerRegistration::register()
• UsersControllerUser::register()
– UsersControllerUser::register() の方は、ユーザー登録許可の設定を確認していない!
– 同じく、外部から権限を示すコードを設定可能
– UsersControllerUser::register()を外部から呼び出す経路が存在した
• どう対策したか?
– UsersControllerUser::register()の削除
• 利用者はどうすればよいか?
– Joomla!のバージョンアップ
Copyright © 2016 HASH Consulting Corp. 5
Demo
CVE-2016-8869等から得られる教訓
• 使わなくなったコードは速やかに削除しよう
• 脆弱性診断の古典的な観点ではあるが…
– ソースを追わないと判らない場合が多い
– 内部構造を熟知していないと、短期間の脆弱性診断では指摘できない
• 脆弱性診断で、「使わないコードを削除する」ように勧めている理由
– 古いコードには脆弱性があるかもしれない
– 拡張を.bak等に変更しているとソースコードが閲覧できてしなう
– デバッグ機能等の場合は、バックドアとして悪用される可能性
• 「古いコード」がこれほどまでに「悪用可能」な例は珍しく、ちょっ
と興奮した
• やはり、古いコードは速やかに削除しよう
Copyright © 2016 HASH Consulting Corp. 6
← 違法ではないが一部不適切
Joomla!には類似脆弱性の”前科”がある
• Joomla! 2.5.2以前に存在した脆弱性
• ユーザー登録時に、権限情報を付与し、
かつバリデーションエラーを発生させる
ことで権限昇格が可能。
• NICT(情報通信研究機構)の研究者のサ
イトが改ざんされる事件に悪用された
Copyright © 2016 HASH Consulting Corp. 7
https://developer.joomla.org/security-centre/395-
20120303-core-privilege-escalation.html より引用
Joomla2.5.2の権限昇格脆弱性
攻撃の流れ
1. 会員登録時にパスワードを不整合にしておく
2. ユーザ登録時に jforms[groups][]=7 をPOSTパラメータに追加
3. バリデーションでエラー発生
4. 再入力に備えてリクエストのパラメータをすべてセッションに
保存(コントローラ)
5. モデル側で、セッションの中味をすべて取り込み
6. 2.で追加したgroupsが取り込まれる
Copyright © 2016 HASH Consulting Corp. 8
Joomla2.5.2の権限昇格脆弱性
components/com_users/controllers/registration.php register()関数
$data = $model->validate($form, $requestData);
// Check for validation errors.
if ($data === false) {
// Save the data in the session.
$app->setUserState('com_users.registration.data', $requestData);
// Redirect back to the registration screen.
$this->setRedirect(JRoute::_('index.php?option=com_users&view=registration', false));
return false;
// 中略
// バリデーションが正常の場合
// Flush the data from the session.
$app->setUserState('com_users.registration.data', null);
Copyright © 2016 HASH Consulting Corp. 9
バリデーションエラーの場合、リクエストデータを
まるごとセッション変数に放り込んでいる
権限の情報も含まれている
components/com_users/models/registration.php getData() 関数内
$temp = (array)$app->getUserState('com_users.registration.data', array());
foreach ($temp as $k => $v) {
$this->data->$k = $v; // セッションのデータをモデルに放り込んでいる
}
【中略】
$this->data->groups = isset($this->data->groups) ?
array_unique($this->data->groups) : array();
// $this->data->groups = array(); 2.5.3でこのように修正
Copyright © 2016 HASH Consulting Corp. 10
セッション汚染、Trust Boundary Violation
と呼ばれる問題
セッション汚染脆弱性とは?
• セッション変数を外部から変更できる脆弱性
• 例: phpMyAdmin CVE-2011-2505
Copyright © 2016 HASH Consulting Corp. 11
if (strstr($_SERVER['QUERY_STRING'],'session_to_unset') != false)
{
parse_str($_SERVER['QUERY_STRING']);
session_write_close();
session_id($session_to_unset);
session_start();
$_SESSION = array();
session_write_close();
session_destroy();
exit;
}
parse_str関数に外部入力を与え、
第二引数なしで使うと、
register_globals以上に危険。
セッション変数の書き換えが可能
libraries/auth/swekey/swekey.auth.lib.php 266行目以降
セッション汚染でセッション変数にオブジェクトを突っ込む
• これができると危険だが…
– 脆弱性(PHPあるいはアプリケーション)がないとできない
• Joomla!みたいにRequestをごそっとセッション変数に代入したり、
parse_str関数を使っていても、オブジェクトは挿入できない
• 以下のようなケース
– PHPの脆弱性を使う
• CVE-2015-6835 (Joomla! に対するゼロデイ攻撃で悪用された)
• CVE-2016-7125 (後述)
– 入力値をunserialize関数を通している
• phpMyAdminの CVE-2011-2505 (前述)
– その他、eval等(オブジェクトインジェクション以前に危険だが…)
• オブジェクトインジェクション以前の問題として危険
Copyright © 2016 HASH Consulting Corp. 12
CVE-2016-7125によるオブジェクト・インジェクション脆弱性
<?php
require_once('Logger.php');
session_start();
?><form action="" method="POST">
name: <input name=name value=<?php ex($_SESSION['name']); ?>><br>
name: <input name=mail value=<?php ex($_SESSION['mail']); ?>><br>
<input type=submit><br> // ex()はHTMLエスケープして表示
</form><?php
if (isset($_POST) && ! validate($_POST)) { // バリデーションエラーの場合は
foreach($_POST as $key => $value) { // セッション変数にリクエスト値を退避
$_SESSION[$key] = $_POST[$key];
}
}
Copyright © 2016 HASH Consulting Corp. 13
脆弱なサンプル(続き)
<?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. 14
脆弱なサンプル(続き)
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. 15
CVE-2016-7125の攻撃
• POSTリクエストに以下を追加
&_SESSION=foo|O:6:"Logger":2:{s:16:"%00Logger%00filename";s:31:"../../../../../var
/www/evil.php";s:11:"%00Logger%00log";s:20:"<?php+phpinfo();+?>%0a";}
• 上記は、Loggerオブジェクトをシリアライズしたもの
– ファイル名: ../../../../../var/www/evil.php
– ログの中身: <?php phpinfo(); ?>
• すなわち、/var/www/evil.phpに、<?php phpinfo(); ?>を書き込む
• 当然ながら、任意のPHPコードが書き込める
• Loggerクラスには、局所的なディレクトリトラバーサル脆弱性がある
ことも原因で、それを対策しておくと攻撃を緩和できる
Copyright © 2016 HASH Consulting Corp. 16
先のサンプルはオブジェクト・インジェクション以前の問題
• 本質的な問題はSession Poisoning
• 以下のような攻撃
• http://example.jp/?userid=yamada&login=1
– $_SESSION[‘userid’] === ‘yamada’
– $_SESSION[‘login’] === ‘1’ (TRUEと評価され得る)
– → セッションハイジャックができてしまう
• すなわち、オブジェクト・インジェクション以前の問題として、セッ
ション汚染単体で問題となる可能性が高い
Copyright © 2016 HASH Consulting Corp. 17
CVE-2016-7125の対策
• PHPを最新版にする
– RHEL / CentOSではパッチが出ていないことに注意
– Debian / Ubuntu / Fedora のサポート中のバージョンは問題ない
• そもそもセッション汚染の状態を避ける
Copyright © 2016 HASH Consulting Corp. 18
https://access.redhat.com/security/cve/cve-2016-7125 より引用
インジェクション系脆弱性の話題
• OSコマンドインジェクション
• 正規表現インジェクション
• SQLインジェクション
• XSS(JavaScriptリテラルのエスケープ)
Copyright © 2016 HASH Consulting Corp. 19
ケータイキット for MovableTypeの
OSコマンドインジェクション脆弱性
Copyright © 2016 HASH Consulting Corp. 20
http://itpro.nikkeibp.co.jp/atcl/news/16/042301210/ より引用 21
ケータイキット for Movable Type の脆弱性 (CVE-2016-1204) に関する注意喚起
各位
JPCERT-AT-2016-0019
JPCERT/CC
2016-04-26(新規)
2016-05-06(更新)
<<< JPCERT/CC Alert 2016-04-26 >>>
ケータイキット for Movable Type の脆弱性 (CVE-2016-1204) に関する注意喚起
https://www.jpcert.or.jp/at/2016/at160019.html
I. 概要
アイデアマンズ株式会社のケータイキット for Movable Type には、OS コマンドインジェクションの脆弱性 (CVE-2016-1204) があります。この
脆弱性を悪用された場合、当該製品が動作するサーバ上で任意の OS コマンドを実行される可能性があります。
本脆弱性や影響の詳細については、以下を参照してください。
Japan Vulnerability Notes JVNVU#92116866
ケータイキット for Movable Type に OS コマンドインジェクションの脆弱性
https://jvn.jp/vu/JVNVU92116866/
なお、本脆弱性を悪用した攻撃活動が確認されているとの情報があります。
https://www.jpcert.or.jp/at/2016/at160019.html より引用 22
日本テレビ 個人情報不正アクセスに関する調査報告書 より引用
23http://www.ntv.co.jp/oshirase/20160714.pdf
脆弱なソースコード(Ver 1.641)
$wallpaper = isset($_GET['mtkk_wallpaper'])? unserialize($_GET['mtkk_wallpaper']): null;
// 中略
$cl = (isset($wallpaper['left']) && $wallpaper['left'])? $wallpaper['left']: 0;
// 中略
$options['left'] = $cl;
// 中略
if($options['left'] != 0 || $options['top'] != 0 || $options['width'] != $id['w']
|| $options['height'] != $id['h']) {
$dest_tmp = "$dest-crop.bmp";
$option = " -crop{$options['width']}x{$options['height']}+{$options['left']}+{$options['t
op']}";
$execute = "$convert $option $src $dest_tmp";
if($this->debug_mode) {
// 中略
} else {
exec($execute);
}
24
対策版(Ver 1.65)
$wallpaper = isset($_GET['mtkk_wallpaper'])? unserialize($_GET['mtkk_wallpaper']): null;
// 中略
$cl = (isset($wallpaper['left']) && $wallpaper['left'])? $wallpaper['left']: 0;
// 中略
$options['left'] = $cl;
// 中略
if($options['left'] != 0 || $options['top'] != 0 || $options['width'] != $id['w']
|| $options['height'] != $id['h']) {
$dest_tmp = "$dest-crop.bmp";
$option = " -crop " . escapeshellarg("{$options['width']}x{$options['height']}+{$options
['left']}+{$options['top']}");
$execute = "$convert $option " . escapeshellarg($src) . " " . escapeshellarg($dest_tmp);
if($this->debug_mode) {
// 中略
} else {
exec($execute);
}
25
OSコマンドインジェクション対策に関して…徳丸の苦悩
• PHPには、シェル経由のコマンド実行関数しか用意されていない
• そのため、安全なコマンド実行には、パラメータのエスケープ処理が
必須となる
• 本当にPHPに用意されたシェルエスケープ関数は安全なのだろうか?
• 試してみよう!
– なんと、escapeshellcmd関数には脆弱性がある!
http://www.tokumaru.org/d/20110101.html#p01
– escapeshellarg関数の方は大丈夫そう…
• 「徳丸本」にはエスケープも含め4種類の対策を書くことに
• エスケープは4種中第4位で、心から信頼しているわけではない
Copyright © 2016 HASH Consulting Corp. 26
徳丸本に学ぶOSコマンドインジェクション対策
1. OS コマンド呼び出しを使わない実装方法を選択する
2. シェル呼び出し機能のある関数の利用を避ける(PHPでは選択でき
ない)
3. 外部から入力された文字列をコマンドラインのパラメータに渡さな
い
4. OS コマンドに渡すパラメータを安全な関数によりエスケープする
Copyright © 2016 HASH Consulting Corp. 27
エスケープは「めんどうくさい」
できれば避けたい
たとえば、エスケープを避ける
Copyright © 2016 HASH Consulting Corp. 28
もっと良い方法があった!
• ポイントはこれだけです
1. シェルスクリプトは静的な文字列として定義する。
2. パラメーターは環境変数で渡す。
3. エスケープ不要!
• 要は SQL のプリペアードステートメントみたいなものです。
29
#!/usr/bin/ruby
def getent_egrep_sed(db, pattern, script)
env = {
'db' => db,
'pattern' => pattern,
'script' => script,
}
system(env, 'getent -- "$db" |egrep -- "$pattern" |sed -- "$script"')
end
getent_egrep_sed(*ARGV[0..2])
https://fumiyas.github.io/2013/12/21/dont-use-shell.sh-advent-calendar.html より引用
PHPによるサンプルコード
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("file", "/tmp/error-output.txt", "a") // stderr .. temporary file
);
$arg2 = "; cat /etc/passwd #ntest test test";
$env = array('e_arg1' => 'aeiou; "xxx"', 'e_arg2' => $arg2);
$process = proc_open('./argdump "$e_arg1" "$e_arg2"', $descriptorspec, $pipes, getcwd(), $env);
if (is_resource($process)) {
fclose($pipes[0]);
while(!feof($pipes[1]))
echo fread($pipes[1], 10);
fclose($pipes[1]);
$return_value = proc_close($process);
echo "command returned $return_valuen";
}
Copyright © 2016 HASH Consulting Corp. 30
ふみやす方式による"安全なsystem関数"
function e_system($cmd, ...$args) {
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/error-output.txt", "a"));
$cmdline = $cmd;
$env = [];
foreach ($args as $key => $value) {
$argname = 'e_arg_' . $key; // これは本当はよろしくない。局所的な脆弱性
$cmdline .= ' "$' . $argname . '"';
$env[$argname] = $value;
}
$process = proc_open($cmdline, $descriptorspec, $pipes, getcwd(), $env);
if (is_resource($process)) {
fclose($pipes[0]);
while(!feof($pipes[1]))
echo fread($pipes[1], 10);
fclose($pipes[1]);
return proc_close($process);
}
return FALSE;
}
e_system('/usr/sbin/sendmail', 'a@example.jp; cat /etc/passwd'); // パラメータは別個の引数として指定
Copyright © 2016 HASH Consulting Corp. 31
改善方針1
function e_system($cmd, ...$args) {
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/error-output.txt", "a"));
$cmdline = $cmd;
$env = [];
for ($i = 0; $i < count($args); $i++) { // わかりやすく for文にしてみました
$argname = 'e_arg_' . $i; // これは本当はよろしくない。局所的な脆弱性
$cmdline .= ' "$' . $argame . '"';
$env[$argname] = $args[$i];
}
$process = proc_open($cmdline, $descriptorspec, $pipes, getcwd(), $env);
if (is_resource($process)) {
fclose($pipes[0]);
while(!feof($pipes[1]))
echo fread($pipes[1], 10);
fclose($pipes[1]);
return proc_close($process);
}
return FALSE;
}
e_system('/usr/sbin/sendmail', 'a@example.jp; cat /etc/passwd'); // パラメータは別個の引数として指定
Copyright © 2016 HASH Consulting Corp. 32
改善方針2 (for文を使うと死んでしまう人向け)
function e_system($cmd, ...$args) {
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/error-output.txt", "a"));
$cmdline = $cmd;
$env = [];
foreach (array_values($args) as $key => $value) { // array_values関数でキー文字列を削除
$argname = 'e_arg_' . $key; // これは本当はよろしくない。局所的な脆弱性
$cmdline .= ' "$' . $argname . '"';
$env[$argname] = $value;
}
$process = proc_open($cmdline, $descriptorspec, $pipes, getcwd(), $env);
if (is_resource($process)) {
fclose($pipes[0]);
while(!feof($pipes[1]))
echo fread($pipes[1], 10);
fclose($pipes[1]);
return proc_close($process);
}
return FALSE;
}
e_system('/usr/sbin/sendmail', 'a@example.jp; cat /etc/passwd'); // パラメータは別個の引数として指定
Copyright © 2016 HASH Consulting Corp. 33
正規表現インジェクション
phpMyAdmin: CVE-2013-3238
Copyright © 2016 HASH Consulting Corp. 34
phpMyAdmin: CVE-2013-3238
Copyright © 2016 HASH Consulting Corp. 35
case 'replace_prefix_tbl':
$current = $selected[$i];
$newtablename = preg_replace("/^" . $from_prefix . "/", $to_prefix, $current);
preg_replace("/^/e0/", "phpinfo();", "test");
preg_replace("/^/e", "phpinfo();", "test");
$from_pref = "/e0“;
$to_prefix = "phpinfo();”;
PHP5.4.3以前では、0以降は無視される
脆弱性が混入した要因
• preg_replaceに渡す正規表現をエスケープしていなかった
– 最低限、/ をエスケープする必要がある
• えーっと、preg_quoteって、マルチバイト対応だっけ?
– Shift_JIS以外では問題ない?
• そもそも、正規表現を外部から(信頼境界を超えて)渡す実装は
避けるべき(実際にもその方向で改修された)
Copyright © 2016 HASH Consulting Corp. 36
preg_replace(“/^” . $from_prefix . “/”, …
↓
reg_replace("/^" . preg_quote($from_prefix, '/') . "/", …
phpMyAdmin 4.6.2 librariestransformations.lib.php
function PMA_Transformation_globalHtmlReplace($buffer, $options = array())
{
if (! isset($options['string'])) {
$options['string'] = '';
}
if (isset($options['regex']) && isset($options['regex_replace'])) {
$buffer = preg_replace(
'@' . str_replace('@', '@', $options['regex']) . '@si',
$options['regex_replace'],
$buffer
);
}
// Replace occurrences of [__BUFFER__] with actual text
$return = str_replace("[__BUFFER__]", $buffer, $options['string']);
return $return;
}
Copyright © 2016 HASH Consulting Corp. 37
正規表現中の @ を @ にエ
スケープしているが不完全
正規表現インジェクションの対策の考え方
• PHPには、正規表現をエスケープする関数 preg_quote が用意されて
いるが…
• そもそも、正規表現を外部から指定できる状況は、極力避けるべき
• 【原則】外部由来の値は、正規表現として利用しない
• phpMyAdminの正規表現インジェクション脆弱性も、正規表現を避け
る形で実装された
Copyright © 2016 HASH Consulting Corp. 38
SQLインジェクション
ZEND Framekwork 1 のSQLインジェクション(昨年の続き)
Copyright © 2016 HASH Consulting Corp. 39
Zend Framework のSQLインジェクション
(CVE-2014-4914)
去年のおさらい及び続き
Copyright © 2008-2015 HASH Consulting Corp. 40
Zend Frameworkとは?
• PHPの心臓部であるZend Engineを開発しているZend Technologies社
が開発したアプリケーションフレームワーク
• 柔軟な構造であり自由な使い方ができる
• 依存関係が弱くコンポーネントとして利用が容易
• PHPのオブジェクト指向を活用している
• …
• 要はZend謹製のフレームワーク
Copyright © 2008-2015 HASH Consulting Corp. 41
Zend_Dbの使い方
require_once 'Zend/Db.php';
$params = array('host' => 'localhost',
'username' => DBUSER,
'password' => DBPASSWD,
'dbname' => DBNAME);
$db = Zend_Db::factory('PDO_MYSQL', $params);
$select = $db->select()
->from('products')
->order('name'); // 列 nameでソート
$result = $db->fetchAll($select);
// 生成されるSQL文
SELECT `products`.* FROM `products` ORDER BY `name` ASC
Copyright © 2008-2015 HASH Consulting Corp. 42
orderメソッドあれこれ
// 単純
order('name') ORDER BY `name` ASC
// 降順
order('name desc') ORDER BY `name` DESC
// 識別子のエスケープ
order('na`me') ORDER BY `na``me` ASC
// 配列による複数ソートキー指定
order(array('name', 'id'))
ORDER BY `name` ASC, `id` ASC
// 式も書けるよ
order('(name + id)') ORDER BY (name + id) ASC
Copyright © 2008-2015 HASH Consulting Corp. 43
ここで一つ疑問ががが
• 識別子はクォートとエスケープがされる
– name → `name`
– na`ma → `na``me`
• 式はそのまま
– (name + id) → (name + id)
• どうやって識別子と式を区別しているの?
• ソースを見よう!
if (preg_match('/(.*)/', $val)) {
$val = new Zend_Db_Expr($val);
}
Copyright © 2008-2015 HASH Consulting Corp. 44
// ( と ) があれば
// 式とみなす
45
(;´Д`)
Copyright © 2008-2015 HASH Consulting Corp.
CVE-2014-4914 (Zend Framework 1.12.6以前)
• orderの引数文字列に ( と ) がありさえすれば式とみなされエスケープ
対象外となる
• 1 ; 攻撃文字列 -- () とかでも おk
SELECT `products`.* FROM `products` ORDER BY 1; 攻撃文字列 -- () ASC
• 公表されたPoCは以下の通り
order('MD5(1); drop table products --')
↓ 生成されるSQL文
SELECT `products`.* FROM `products` ORDER BY MD5(1); drop table products --
ASC
• 参考: http://framework.zend.com/security/advisory/ZF2014-04
Copyright © 2008-2015 HASH Consulting Corp. 46
Zend Framework 1.12.7 での修正
• 式の判定が以下のように修正された
// 1.12.6以前
if (preg_match('/(.*)/', $val)) {
$val = new Zend_Db_Expr($val);
}
// 1.12.7
if (preg_match('/^[w]*(.*)$/', $val)) {
$val = new Zend_Db_Expr($val);
}
// 英数字0文字以上に続けて ( があり、末尾に ) があれば式とみなす
Copyright © 2008-2015 HASH Consulting Corp. 47
48
(;´Д`)
Copyright © 2008-2015 HASH Consulting Corp.
Zend Framework 1.12.7 に対する攻撃
• 従来のPoC
order('MD5(1); drop table products --')
↓ 生成されるSQL文
SELECT `products`.* FROM `products` ORDER BY `MD5(1); drop table products --`
ASC
// order by 以降が ` で囲まれて識別子となる
• 新しいPoC
order('MD5(1); drop table products -- )')
↓ 生成されるSQL文
SELECT `products`.* FROM `products` ORDER BY MD5(1); drop table products -- )
ASC
// 式とみなされる条件を満たすので「そのまま」SQL文に
Copyright © 2008-2015 HASH Consulting Corp. 49
Zend Framework 1.12.8 での修正
• 式の判定が以下のように修正された
// 1.12.7
if (preg_match('/^[w]*(.*)$/', $val)) {
$val = new Zend_Db_Expr($val);
}
// 英数字0文字以上に続けて ( があり、末尾に ) があれば式とみなす
// 1.12.8
if (preg_match('/^[w]*([^)]*)$/', $val)) {
$val = new Zend_Db_Expr($val);
}
// 英数字0文字以上に続けて ( があり、途中は ) 以外が続き、
// 末尾に ) があれば式とみなす
Copyright © 2008-2015 HASH Consulting Corp. 50
51
(;´Д`)
Copyright © 2008-2015 HASH Consulting Corp.
これはフレームワークの脆弱性なのか?
• Ruby on Railsの場合、orderメソッドに指定する文字列は「式」と決
まっているので、アプリケーション側のバリデーション等で対策する
ことが求められる
• Zend Frameworkの場合は、文字列の内容により識別子か式かが決ま
るので、責任境界があいまい
• 本来、識別子用のメソッドと式用のメソッドは、名前などで明確に区
別するべし
• つまり、Zend Frameworkの仕様の問題である
Copyright © 2008-2015 HASH Consulting Corp. 52
これで終わったと徳丸は思っていた
Copyright © 2016 HASH Consulting Corp. 53
だが、終わりではなかった
Copyright © 2016 HASH Consulting Corp. 54
Zend Framework 1.12.10 での修正
• Zend Framework 1.12.10で関数呼び出し(括弧)のネストを許容した
• 式の判定が以下のように修正された
// 1.12.10
if (preg_match('/^([w]*(([^)]|(?1))*))$/', (string) $val)) {
$val = new Zend_Db_Expr($val);
}
Copyright © 2008-2015 HASH Consulting Corp. 55
https://regexper.com/ を利用
Zend Framework 1.12.10 での修正(続き)
• この修正により、以下のようなORDER BY句が許容された
– ORDER BY ((list_price - discount_price) * quantity)
• だが、穴はないのか?
• Zend Framework 1.12.10で混入した脆弱性
– MD5("(");DELETE FROM p2; #)
Copyright © 2008-2015 HASH Consulting Corp. 56
https://regexper.com/ を利用
57
(;´Д`)
Copyright © 2008-2015 HASH Consulting Corp.
Zend Framework 1.12.19 での修正
• 式の判定が以下のように修正された
// 1.12.19
if (preg_match'/^([w]+s*(([^()]|(?1))*))$/', (string) $val)) {
$val = new Zend_Db_Expr($val);
}
Copyright © 2008-2015 HASH Consulting Corp. 58
禁止文字に開き括
弧が追加された
https://regexper.com/ を利用
59
(;´Д`)
Copyright © 2008-2015 HASH Consulting Corp.
Zend Framework 1.12.19への攻撃
• 穴はないのか?
• Zend Framework 1.12.19に対する攻撃
– MD5(“a(");DELETE FROM p2; #)
Copyright © 2008-2015 HASH Consulting Corp. 60
https://regexper.com/ を利用
Zend Framework 1.12.20 での修正
• ORDER BY句のチェックに先立ち、SQLコメントを取り除く処理が追
加された
Copyright © 2008-2015 HASH Consulting Corp. 61
const REGEX_SQL_COMMENTS = '@
((['"]).*?[^]2) # $1 : Skip single & double quoted expressions
|( # $3 : Match comments
(?:#|--).*?$ # - Single line comments
| # - Multi line (nested) comments
/* # . comment open marker
(?: [^/*] # . non comment-marker characters
|/(?!*) # . ! not a comment open
|*(?!/) # . ! not a comment close
|(?R) # . recursive case
)* # . repeat eventually
*/ # . comment close marker
)s* # Trim after comments
|(?<=;)s+ # Trim after semi-colon
@msx';
62
(;´Д`)
Copyright © 2008-2015 HASH Consulting Corp.
Zend Framework 1.12.20 への攻撃はできないのか?
• 実はまだ穴は残っている (;´Д`)
• Zend Framework 1 は2016年9月28日でEnd of Life(EOL)となった
https://framework.zend.com/blog/2016-06-28-zf1-eol.html
• すなわち、もう改修の見込みはない
• ダメ元で報告したけど、「アプリケーション側で対応せよ」という回答だった
• 実は僕もそう思う
Copyright © 2008-2015 HASH Consulting Corp. 63
対策
• Zend Framework 1 をお使いの方は…
– 最新のZend Framework 1.12.20 に移行する かつ
– orderメソッドの引数をバリデーション
• できるだけ早期にZend Framework 2 または 3、あるいはその他のフ
レームワークに移行しましょう
※ Zend Framework 2 にはこの問題はありません
Copyright © 2008-2015 HASH Consulting Corp. 64
XSS(JavaScriptリテラルのエスケープ)
Copyright © 2016 HASH Consulting Corp. 65
JavaScriptの文字列リテラルのエスケープは難しい
• イベントハンドラ onxxxx= のエスケープは、
– まず文字列をJavaScript文字列としてエスケープして、
– HTMLの属性値としてエスケープする
• script要素の中身のエスケープは、
– JavaScript文字列としてエスケープするだけでよいが、
– </script>を注入されることによる攻撃の対策が必要となる
– 以下のようなケース
<script>
foo("☆ここを動的生成☆");
</script>
Copyright © 2016 HASH Consulting Corp. 66
まちがった例
<script>
foo("☆ここを動的生成☆");
</script>
◎HTMLエスケープした場合 "); alert("1 を注入
<script>
foo(""); alert("1"); // ← alert("1") が注入される
</script>
◎最低限のJavaScript文字列エスケープした場合 </script><script>alert(1) // を注入
<script>
foo("</script><script>alert(1) //");
</script>
※ 攻撃に " '  は使えない
Copyright © 2016 HASH Consulting Corp. 67
script要素内の文字列リテラル動的生成アプローチ
• 過剰エスケープ
– 徳丸本が紹介している方法の一つ
– 英数字以外を全てユニコードエスケープ uXXXX とエスケープする
– 実はあまり好きではない
• HTMLノードとして文字列を生成して、JavaScriptから参照する
– 古典的にはhiddenフィールド(徳丸本で紹介している方法の一つ)
– 最近だと id="hoge" data-foo="<% bar %>" しておいて $("#hoge").data('foo')
でとりだすのが主流かと思います。
http://b.hatena.ne.jp/entry/blog.ohgaki.net/javascript-string-escape
• 奥一穂氏の提唱する方法(Inline JSONP)
– ひとことでいうと、JSONPと同形式の呼出をサーバサイドで生成しSCRIPTタグ
として埋め込む、という手法を採るべきだと思います。
http://d.hatena.ne.jp/kazuhooku/20131106/1383690938
Copyright © 2016 HASH Consulting Corp. 68
それぞれの方式の出力結果
• 過剰エスケープ
<script>
foo('u003cu002fscriptu003eu003cscriptu003ealertu00281u0029u002fu002f');
</script>
• data-*方式
<div id="hoge" data-foo="&lt;/script&gt;&lt;script&gt;alert(1)//">何か</div>
<script>
foo(document.getElementById("hoge").dataset.foo);
</script>
• Inline JSONP
<script>
foo({"name":"u003C/scriptu003Eu003Cscriptu003Ealert(1)//"});
</script>
Copyright © 2016 HASH Consulting Corp. 69
なぜJavaScriptの動的生成を避けるべきか?
• コードとデータは分離すべき
• HTMLエスケープとJSONのエスケープは、テンプレートエンジンや
JSONライブラリが勝手にやってくれるが、JavaScriptのエスケープは、
必ずしもそうではない
• JavaScriptのエスケープは、ややこしすぎて人知を超えている
• よって、HTMLノード経由にするか、Inline JSONPを採用するのが良い
と考えます
Copyright © 2016 HASH Consulting Corp. 70
安全なアプリケーションの作り方
Copyright © 2016 HASH Consulting Corp. 71
安全なウェブアプリケーションのための原則
• わかりやすく書こう、うますぎるプログラムはいけない
• 局所的に脆弱性を解消する
• 防御的プログラミングを実践する
• 単体テストを徹底する
• コード、命令に対して、外部からの値を持ち込まない
• 危険な機能の利用を極力避ける
Copyright © 2016 HASH Consulting Corp. 72
わかりやすく書こう-うますぎるプログラムはいけない*1
• こなれた機能を使ってシンプルに実装すること
• 「危険な機能」は原則として避ける
– eval、system、call_user_func …
– 複雑な正規表現も避けた方が良い
Copyright © 2016 HASH Consulting Corp. 73
const REGEX_SQL_COMMENTS = '@
((['"]).*?[^]2) # $1 : Skip single & double quoted expressions
|( # $3 : Match comments
(?:#|--).*?$ # - Single line comments
| # - Multi line (nested) comments
/* # . comment open marker
(?: [^/*] # . non comment-marker characters
|/(?!*) # . ! not a comment open
|*(?!/) # . ! not a comment close
|(?R) # . recursive case
)* # . repeat eventually
*/ # . comment close marker
)s* # Trim after comments
|(?<=;)s+ # Trim after semi-colon
@msx'; Zend Framework 1.12.20 に出て来る正規表現
*1 プログラム書法、 B.カーニハン著、木村泉訳より引用
防御的プログラミング
• 関数等の引数が想定したものかをモジュール側で確認する
• 関数等の戻り値が想定したものかどうかを呼び出し側で確認する
74
「防御的プログラミング」とはプログラミングに対して防御的になること、つまり「そうなるはずだ」と決め付けないこと
である。この発想は「防御運転」にヒントを得たものだ。防御運転では、他のドライバーが何をしようとするかまったくわ
からないと考える。そうすることで、他のドライバーが危険な行動に出たときに、自分に被害が及ばないようにする。たと
え他のドライバーの過失であっても、自分の身は自分で守ることに責任を持つ。同様に、防御的プログラミングの根底にあ
るのは、ルーチンに不正なデータが渡されたときに、それが他のルーチンのせいであったとしても、被害を受けないように
することだ。もう少し一般的に言うと、プログラムには必ず問題があり、プログラムは変更されるものであり、賢いプログ
ラマはそれを踏まえてコードを開発する、という認識を持つことである。
CODE COMPLETE 第2版 第8章 から引用
防衛的プログラミング [Defensive programming]
潜在的なエラーに対抗するため、すべてのモジュールに膨大な一貫性チェックを行わせる手法(たとえ供給者と顧客の双方
によって冗長な検査が行われるとしても気にしない)。契約による設計の精神には反する。(同書 P713)
契約による設計[Design by Contract]
システムのコンポーネント同士が、互いに厳密な契約を介して協調するように設計するソフトウェア構築手法。防衛的プロ
グラミングも見よ。(同書 P707)
バートランド・メイヤー著、オブジェクト指向入門第二版 方法論・実践 より引用
安全なウェブアプリケーションのための原則
• コード、命令に対して、外部からの値を持ち込まない
– プログラム(JavaScript含む)、SQL文、シェルコマンド、ファイル名、正規表現
– HTMLとJSONは適当な方法がないのでエスケープで…
• 局所的に脆弱性を解消する
• 「ややこしいことが起きがちな」機能を避ける
– eval、system、call_user_func …
– 複雑な正規表現も避けた方が良い
– デストラクタ等に複雑な処理を書かない
• 防御的プログラミングを実践する
• 単体テストを徹底する
Copyright © 2016 HASH Consulting Corp. 75
1 of 75

More Related Content

What's hot(20)

イエラエセキュリティMeet up 20210820イエラエセキュリティMeet up 20210820
イエラエセキュリティMeet up 20210820
GMOサイバーセキュリティ byイエラエ株式会社1.4K views
Bug bountyBug bounty
Bug bounty
n|u - The Open Security Community5.2K views
Bug Bounty Secrets Bug Bounty Secrets
Bug Bounty Secrets
n|u - The Open Security Community6.2K views
とある診断員とAWSとある診断員とAWS
とある診断員とAWS
zaki464940.7K views
Webアプリって奥が深いんですWebアプリって奥が深いんです
Webアプリって奥が深いんです
abend_cve_9999_00013K views
ネットワーク脆弱性診断の簡単な説明ネットワーク脆弱性診断の簡単な説明
ネットワーク脆弱性診断の簡単な説明
株式会社テリロジー 技術統括部5K views
いまさら、AWSのネットワーク設計いまさら、AWSのネットワーク設計
いまさら、AWSのネットワーク設計
Serverworks Co.,Ltd.28K views
AWS BlackBelt AWS上でのDDoS対策AWS BlackBelt AWS上でのDDoS対策
AWS BlackBelt AWS上でのDDoS対策
Amazon Web Services Japan32.3K views
Web Application Security TestingWeb Application Security Testing
Web Application Security Testing
Marco Morana17.6K views
ホワイトハッカー育成講座(3) ~SHODANの活用~ホワイトハッカー育成講座(3) ~SHODANの活用~
ホワイトハッカー育成講座(3) ~SHODANの活用~
株式会社テリロジー 技術統括部6.6K views

Similar to 安全なPHPアプリケーションの作り方2016(20)

Webシステム脆弱性LT資料Webシステム脆弱性LT資料
Webシステム脆弱性LT資料
Tomohito Adachi1.1K views
20160208 power cms_cloud_public20160208 power cms_cloud_public
20160208 power cms_cloud_public
Six Apart910 views
20160209 power cms_cloud_public20160209 power cms_cloud_public
20160209 power cms_cloud_public
Six Apart1.3K views
PowerShell and  Release Management ServerPowerShell and  Release Management Server
PowerShell and Release Management Server
Kazushi Kamegawa2.2K views
2017年のセキュリティ 傾向と対策講座2017年のセキュリティ 傾向と対策講座
2017年のセキュリティ 傾向と対策講座
NHN テコラス株式会社2K views

More from Hiroshi Tokumaru(17)

SQLインジェクション再考SQLインジェクション再考
SQLインジェクション再考
Hiroshi Tokumaru1.1K views
秀スクリプトの話秀スクリプトの話
秀スクリプトの話
Hiroshi Tokumaru31.4K views
ウェブセキュリティの常識ウェブセキュリティの常識
ウェブセキュリティの常識
Hiroshi Tokumaru31.4K views
セキュリティの都市伝説を暴くセキュリティの都市伝説を暴く
セキュリティの都市伝説を暴く
Hiroshi Tokumaru87.1K views
Rails SQL Injection Examplesの紹介Rails SQL Injection Examplesの紹介
Rails SQL Injection Examplesの紹介
Hiroshi Tokumaru14.2K views

安全なPHPアプリケーションの作り方2016