『例えば、PHPを避ける』以降PHPはどれだけ
安全になったか
徳丸 浩
アジェンダ
• 例えば、PHPを避ける
• htmlspecialchars 文字エンコーディングチェックの改善
• register_globalsが非推奨に
• マジッククォートが非推奨に
• 暗号学的に安全な擬似乱数生成器のサポート
• セッションID生成の安全性強化
• ヌルバイト攻撃の防御機能の追加
• PDOのDB接続時の文字エンコーディング指定が可能に
• header関数のバグ修正
• 安全なパスワード保存が簡単にできるようになった
Copyright © 2016 Hiroshi Tokumaru 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月)
– 技術士(情報工学部門)
Copyright © 2016 Hiroshi Tokumaru 3
例えば、PHPを避ける
Copyright © 2016 Hiroshi Tokumaru 4
Copyright © 2016 Hiroshi Tokumaru 5
例えば、PHPを避ける
現在のセキュアプログラミング講座
6
register globalsとナルバイト攻撃が問題???
https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/003.html より引用
PHPの何が問題だったか、本当に問題だったか?
• セキュアプログラミング講座の改訂は2007年6月
• その時点で、PHPは本当に避けるべき存在だった
か?
• 当時の最新版はPHP 5.2.3 (2007/5/31)
• その後、PHPはどの程度の安全になったか?
• 下記について調査
– PHPの安全でない機能の削除
– PHPの安全性を高める機能の追加
Copyright © 2016 Hiroshi Tokumaru 7
htmlspecialchars 文字エンコーディング
チェックの改善(PHP5.2.5 2007/11/8)
Copyright © 2016 Hiroshi Tokumaru 8
htmlspecialcharsの文字コードチェックの変遷
• PHP4.1.0 (2001/12/10)
htmlspecialcharsに第3引数追加。ほとんど何もして
いないに等しい文字エンコーディングチェック
• PHP-5.2.5 (2007/11/8)
文字エンコーディングのチェックを強化…したけど
抜けがたくさん
• PHP-5.2.12 (2009/12/17)
moriyoshiの神対応による厳格なチェックに
• PHP-5.4.0 (2012/3/1)
第3引数のデフォルトが UTF-8 に変更
Copyright © 2016 Hiroshi Tokumaru 9
そこそこ安全なはずのスクリプト
Copyright © 2016 Hiroshi Tokumaru 10
<?php
header('Content-Type: text/html; charset=Shift_JIS');
$p1 = @$_GET['p1'];
$p2 = @$_GET['p2'];
?><body><form>
<input name=p1 value="<?php echo
htmlspecialchars($p1, ENT_QUOTES, 'Shift_JIS'); ?>"><BR>
<input name=p2 value="<?php echo
htmlspecialchars($p2, ENT_QUOTES, 'Shift_JIS'); ?>"><BR>
<input type="submit" value="更新">
</form></body>
半端な先行バイトによるXSS
• 半端な先行バイトによるXSSが発生する条件は、
以下のいずれかを満たす場合
– htmlspecialcharsの第3引数を指定していない
– PHPの5.2.11以前あるいはPHP5.3.1以前を使用
• 対策としては、以下の両方を行う
– PHPの最新版を使う
– htmlspecialcharsの第3引数を指定する
Copyright © 2010-2014 HASH Consulting Corp. 11
<input name=p1 value="・><BR>
<input name=p2 value=" onmouseover=alert(document.cookie)//"><BR>
閉じる引用符が食われた状態
ここで最初の属性値がようやく終了 第二の属性値がイベントハンドラに
register_globalsが非推奨に
(PHP-5.3.0 2009/6/30)
Copyright © 2016 Hiroshi Tokumaru 12
register_globals=On の危険な例
session_start();
if (isset($_SESSION['user'])) {
$islogin = TRUE;
}
Copyright © 2016 Hiroshi Tokumaru 13
履歴
• PHP-4.2.0 (2002/4/22)
register_globalsがデフォルトで off になる
• PHP-5.3.0 (2009/6/30)
register_globalsを有効にすると警告エラーになる
• PHP-5.4.0 (2012/3/1)
register_globalsが廃止される
Copyright © 2016 Hiroshi Tokumaru 14
マジッククォートが非推奨に
(PHP-5.3.0 2009/6/30)
Copyright © 2016 Hiroshi Tokumaru 15
マジッククォートとは何か?
• 入力値($_GET、$_POST、$_COOKIE)を予めエスケー
プしておく設定
– ' → '  → 
• SQLインジェクション対策の自動化のために導入さ
れた
• PHP-5.3.0 (2009/6/30)
マジッククォートを有効にすると警告エラーになる
• PHP-5.4.0 (2012/3/1)
マジッククォートが廃止される
Copyright © 2016 Hiroshi Tokumaru 16
マジッククォートはなぜダメだったか?
• 不便
– システムが勝手にエスケープするので多重エスケープの原因になる
– エスケープが不要な場合アンエスケープの必要があり、不便であり、
脆弱性の要因にもなる
• 対策として不十分(文字エンコーディングを考慮しないため
…PDOの項参照)
• 徳丸の意見
– マジッククォートは入力時にエスケープ処理を自動的行う仕組みだ
が、エスケープ処理は文字列を使う時に都度すべきという考え方が
一般化した
– マジッククォートはMySQLに特化したエスケープ方式であり、かつ
MySQLのオプションや文字エンコーディングを考慮しない不完全な
エスケープだった
Copyright © 2016 Hiroshi Tokumaru 17
デモメモ:
ここでphp.iniを編集して、
register_globalsとmagic_quotes_gpcを
オフにしておく
Copyright © 2016 Hiroshi Tokumaru 18
暗号学的に安全な擬似乱数生成器の
サポート(PHP-5.3.0 2009/6/30)
Copyright © 2016 Hiroshi Tokumaru 19
PHPにおける乱数の状況
• PHPにおける乱数の状況は酷い…下記が用いられる
– rand()
– mt_rand()
– uniqid()
• 上記はいずれも暗号学的に安全でない
– 過去の乱数列から推測可能性があるということ
• PHP-5.3.0から下記がサポートされる
– openssl_random_pseudo_bytes()
• PHP-7.0.0から下記がサポートされる
– random_bytes()
– random_int()
Copyright © 2016 Hiroshi Tokumaru 20
セッションID生成の安全性強化
(PHP-5.3.2 2010/3/4)
Copyright © 2016 Hiroshi Tokumaru 21
22体系的に学ぶ 安全なWebアプリケーションの作り方 P162、163より引用
PHPはデフォルト設定では以下の組み合わせにMD5ハッシュ関数を通す方法でセッ
ションIDを生成しています。
 リモートIP アドレス
 現在時刻
 乱数(暗号論的擬似乱数生成系ではない)
これは、図4-51で示したありがちなセッションIDの生成方法に該当します。ロジックの
複雑性が高いため解読方法が判明しているわけではありませんが、理論的には安全性
が保証されていない設計ということになります。
23http://dsas.blog.klab.org/archives/52136166.html より引用
セッションIDの強化の歴史
• PHP-5.3.2 (2010/3/4)
セッションIDの生成方法を複雑化したが不完全
• PHP-5.4.0 (2012/3/1)
セッションIDのシードに安全な乱数を使うように
• PHP-5.4未満の場合は下記を設定するとよい
Copyright © 2016 Hiroshi Tokumaru 24
[Session]
;; entropy_file は Windowsでは設定不要
;; PHP-5.4以降では下記がデフォルトに
session.entropy_file = /dev/urandom
session.entropy_length = 32
ヌルバイト攻撃の防御機能の追加
(PHP-5.3.4 2010/12/9)
Copyright © 2016 Hiroshi Tokumaru 25
ヌルバイト攻撃はディレクトリトラバーサル等と併用する
26https://www.ipa.go.jp/security/vuln/websecurity.html より引用
ディレクトリトラバーサルとヌルバイト攻撃
• 以下のPHPスクリプト
$fp = fopen('./data/' . $_GET['file'] . '.txt', 'r');
…
• file=../../../../../etc/passwd%00 とすると
ファイル名は以下となる。[nul]は値0の文字
./data/../../../../../etc/passwd[nul].txt
カレントディレクトリが /var/www/html とすると
/var/www/html/../../../../../etc/passwd[nul].txt
↓ 正規化 ([nul]以降は無視される)
/etc/passwd
• Unix / Linux / WindowsのAPIでは通常ヌルバイトを
文字列の終端記号として用いているため
Copyright © 2016 HASH Consulting Corp. 27
ディレクトリトラバーサルの影響と対策
• 影響
– 任意のファイルの読み出し
– 任意のファイルに任意内容が書き込みできる場合も
– PHPスクリプト等を書き込みできれば、任意スクリプト
を外部から自由に実行できる場合も
• 対策
– ファイル名には basename()関数を通してから使う
$file = basename($_GET['file']);
– できるだけ新しいPHPを使う
• PHP5.3.4以降ではヌルバイト攻撃対策がされている
Copyright © 2016 HASH Consulting Corp. 28
PHP 5.3.4におけるヌルバイト攻撃対策
• ファイル名等にヌルバイトが混入している場合、エ
ラーとして処理を打ち切る
Copyright © 2016 HASH Consulting Corp. 29
$ cat nullbyte.php
<?php
$rtn = readfile("../../../../etc/passwd0.txt");
var_dump($rtn);
$ php-5.3.3 nullbyte.php
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
【中略】
int(1415)
$ php-5.3.4 nullbyte.php
bool(false)
phpMyAdmin3.5.8に存在した正規表現インジェクション
• テーブル名のプリフィックスを変更する処理に存在
Copyright © 2016 HASH Consulting Corp. 30
// $from_prefix, $to_prefix, $currentは外部から操作可能
$newtablename = preg_replace("/^" . $from_prefix . "/",
$to_prefix, $current);
攻撃ができる理由
Copyright © 2016 HASH Consulting Corp. 31
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"
PHP5.4.3以前では、0以降は無視される
/e 修飾子…
32http://www.php.net/manual/ja/reference.pcre.pattern.modifiers.php
脆弱性が混入した要因
• preg_replaceに渡す正規表現をエスケープしていな
かった
– 最低限、/ をエスケープする必要がある…理論的には
• えーっと、preg_quoteって、マルチバイト対応だっ
け?
– Shift_JIS以外では問題ない?
• 外部からの値を用いて正規表現を組み立てるべきで
はない
Copyright © 2016 HASH Consulting Corp. 33
preg_replace(“/^” . $from_prefix . “/”, …
↓
reg_replace("/^" . preg_quote($from_prefix, '/') . "/", …
PDOのDB接続時の文字エンコーディング
指定が可能に(PHP-5.3.6 2011/3/17)
Copyright © 2016 Hiroshi Tokumaru 34
【文字コードの問題1】 5C問題によるSQLインジェクション
• 5C問題とは
– Shift_JIS文字の2バイト目に0x5Cが来る文字に起因する問
題
ソ、表、能、欺、申、暴、十 … など出現頻度の高い文字
が多い
– 0x5CがASCIIではバックスラッシュであり、ISO-8859-1な
ど1バイト文字と解釈された場合、日本語の1バイトが
バックスラッシュとして取り扱われる
– 一貫して1バイト文字として取り扱われれば脆弱性になら
ないが、1バイト文字として取り扱われる場合と、
Shift_JISとして取り扱われる場合が混在すると脆弱性が発
生する
Copyright © 2014 HASH Consulting Corp. 35
ソースコード(要点のみ)
<?php
header('Content-Type: text/html; charset=Shift_JIS');
$key = @$_GET['name'];
if (! mb_check_encoding($key, 'Shift_JIS')) {
die('文字エンコーディングが不正です');
}
// MySQLに接続(PDO)
$dbh = new PDO('mysql:host=localhost;dbname=books;charset=sjis',
'phpcon', 'pass1');
// Shift_JISを指定
$dbh->query("SET NAMES sjis");
// プレースホルダによるSQLインジェクション対策
$sth = $dbh->prepare("SELECT * FROM books WHERE author=?");
$sth->setFetchMode(PDO::FETCH_NUM);
// バインドとクエリ実行
$sth->execute(array($key));
?>
Copyright © 2014 HASH Consulting Corp. 36
5C問題によるSQLインジェクションの説明
Copyright © 2014 HASH Consulting Corp. 37
SQLインジェクション対策はプレースホルダで
• プレースホルダとは
SELECT * FROM books WHERE id=?
• 静的プレースホルダと動的プレースホルダ
– 静的: サーバー側で値をバインドする(エスケープは必要
ない)
– 動的: 呼び出し側で値をエスケープしてバインドする
• 接続時に文字エンコーディングを指定する
$db = new PDO('mysql:host=myhost;dbname=mydb;charset=utf8',
DBUSER, DBPASS);
– SET NAMES utf8 はやめましょう
• 列の型を意識する
Copyright © 2012-2015 HASH Consulting Corp. 38
サンプルコード
$db = new PDO('mysql:host=myhost;dbname=mydb;charset=utf8',
DBUSER, DBPASS);
// エミュレーションモードOFF = 静的プレースホルダ
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// エラー時に例外を発生させる
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// プレースホルダを使ってSQLを準備
$prepare = $db->prepare(
'SELECT * FROM example WHERE id = :id and language = :lang');
// 型を指定してbind
$prepare->bindValue(':id', (int) $id, PDO::PARAM_INT);
$prepare->bindValue(':lang', $str, PDO::PARAM_STR);
$prepare->execute();
Copyright © 2012-2015 HASH Consulting Corp. 39
header関数のバグ修正
(PHP-5.4.0 2012/3/1)
Copyright © 2016 Hiroshi Tokumaru 40
HTTPヘッダインジェクション
41安全なウェブサイトの作り方改訂第7版より引用
このサンプルプログラムでどこまで悪用できるか?
Copyright © 2016 Hiroshi Tokumaru 42
<?php
header('Location: ' . $_GET['url']);
ヘッダインジェクションによるXSSフィルタ回避
Copyright © 2016 Hiroshi Tokumaru 43
<?php
$cookie = $_GET['cookie'];
$txt = $_GET['txt'];
header('Content-Type: text/html; charset="UTF-8"');
header('Set-Cookie: A=' . $cookie);
?><!DOCTYPE html> <body><?php
echo "Cookie A=" . htmlspecialchars($_COOKIE['A']) ; ?>
<?php
echo $txt; // XSS脆弱性あり
?></body>
X-XSS-Protection: 0 ヘッダを追加して、XSSフィルタを無効にしたい…
header関数改修の歴史
• PHP 5.1.2
– 改行のチェックが追加される
– %0d(キャリッジリターン)のチェックが漏れていた
• PHP 5.3.11 および PHP 5.4.0
– キャリッジリターンのチェックが追加される
– 継続行(下図)については許容
• PHP 5.4.38 / PHP 5.5.22 / PHP 5.6.6
– 継続行が禁止される
Copyright © 2016 Hiroshi Tokumaru 44
Location: http://php.net/
Set-Cookie: PHPSESSID=ABC;
↑空白またはタブ
Linuxディストリビューションの対応
Copyright © 2016 Hiroshi Tokumaru 45
※CentOS5 ~ 7 / Ubuntu 14.04以前はパッチが出ていない
安全なパスワード保存が簡単にできるよ
うになった(PHP-5.5.0 2013/6/20)
Copyright © 2016 Hiroshi Tokumaru 46
password_hash 関数 (PHP5.5から)
http://php.net/manual/ja/function.password-hash.php より引用
<?php echo password_hash('rasmuslerdorf’, PASSWORD_DEFAULT);
【結果】
$2y$10$.vGA1O9wmRjrwAVXD98HNOgsNpDczlqm3Jq7KnEd1rVAGv3Fykk1a
47
まとめ
• 『例えば、PHPを避ける』の時代は、現実にPHPの
罠はかなりあった
– 『避ける』必要はないにしても、気をつけなければ脆弱
性の原因に…
• 2016年現在で、罠はかなり解消されつつある
• 特に以下は重要
– Htmlspecialcharsの文字エンコーディングチェック
– PDOのDB接続時の文字エンコーディング指定
• できるだけ新しいバージョンのPHPを使う
• でも、もっと大切なことは、最新のパッチのあたっ
たPHPを使うこと
• PHPを使って、安全で素晴らしいサイト構築を!
Copyright © 2016 Hiroshi Tokumaru 48
ご清聴ありがとうございました
Copyright © 2016 Hiroshi Tokumaru 49

『例えば、PHPを避ける』以降PHPはどれだけ安全になったか