More Related Content Similar to 徳丸本に学ぶ 安全なPHPアプリ開発の鉄則2011 Similar to 徳丸本に学ぶ 安全なPHPアプリ開発の鉄則2011 (20) More from Hiroshi Tokumaru More from Hiroshi Tokumaru (15) 徳丸本に学ぶ 安全なPHPアプリ開発の鉄則20113. 徳丸浩の自己紹介
• 経歴
– 1985年 京セラ株式会社入社
– 1995年 京セラコミュニケーションシステム株式会社(KCCS)に出向・転籍
– 2008年 KCCS退職、HASHコンサルティング株式会社設立
• 経験したこと
– 京セラ入社当時はCAD、計算幾何学、数値シミュレーションなどを担当
– その後、企業向けパッケージソフトの企画・開発・事業化を担当
– 1999年から、携帯電話向けインフラ、プラットフォームの企画・開発を担当
Webアプリケーションのセキュリティ問題に直面、研究、社内展開、寄稿などを開始
– 2004年にKCCS社内ベンチャーとしてWebアプリケーションセキュリティ事業を立ち上げ
• その他
– 1990年にPascalコンパイラをCabezonを開発、オープンソースで公開
「大学時代のPascal演習がCabezonでした」という方にお目にかかることも
• 現在
– HASHコンサルティング株式会社 代表 http://www.hash-c.co.jp/
– 京セラコミュニケーションシステム株式会社 技術顧問 http://www.kccs.co.jp/security/
– 独立行政法人情報処理推進機構 非常勤研究員 http://www.ipa.go.jp/security/
Copyright © 2011 HASH Consulting Corp. 3
4. 本を書きました
2011年3月5日初版第1刷
2011年7月28日 初版第4刷
4
10. 一見対策している*つもり*の例
しかし、このプログラムではIDさえ指定すれば、誰でもどの投稿でも削除でき
てしまいます。そこで、様々なチェックを行ってから削除しています。
if (isset($_SESSION['id'])) {
$id = $_REQUEST['id'];
// 投稿を検査する
$sql = sprintf('SELECT * FROM posts WHERE id=%d',
mysql_real_escape_string($id));
$record = mysql_query($sql) or die(mysql_error());
$table = mysql_fetch_assoc($record);
if ($table['member_id'] == $_SESSION['id']) {
// 削除
mysql_query('DELETE FROM posts WHERE id=' .
mysql_real_escape_string($id)) or die(mysql_error());
}
}
よくわかるPHPの教科書、たにぐちまこと著、毎日コミュニケーションズ、2010より引用 10
11. 一見対策している*つもり*の例
しかし、このプログラムではIDさえ指定すれば、誰でもどの投稿でも削除でき
てしまいます。そこで、様々なチェックを行ってから削除しています。
if (isset($_SESSION['id'])) {
$id = $_REQUEST['id']; id=13 OR TRUE を指定
// 投稿を検査する 投稿のオーナーであることのチェック
$sql = sprintf('SELECT * FROM posts WHERE id=%d',
mysql_real_escape_string($id));
SELECT * FROM posts WHERE id=13
$record = mysql_query($sql) or die(mysql_error());
$table = mysql_fetch_assoc($record);
if ($table['member_id'] == $_SESSION['id']) {
// 削除 このユーザの投稿であることを確認
mysql_query('DELETE FROM posts WHERE id=' .
mysql_real_escape_string($id)) or die(mysql_error());
DELETE FROM posts WHERE id=13 OR TRUE
}
}
全ての投稿を削除
よくわかるPHPの教科書、たにぐちまこと著、毎日コミュニケーションズ、2010より引用 11
14. 入力処理では何をするか
• 文字エンコーディングの妥当性検証
• 文字エンコーディングの変換
• 入力値検証(バリデーション)
– バリデーションはアプリケーションの正常な動作を担保するためで、
脆弱性対策ではない
– 入力値の文字種と文字数のチェック
– 必須項目が入力されているか
• セキュリティの観点からは以下に注意
– 制御文字のチェック(ヌルバイト、改行、その他の制御文字)
– いわゆるブラックリスト検査をするのではなく、正常系の文字種を定
義しよう(いわゆるホワイトリスト検査)
• よくヌルバイトや改行を弾くサンプルを見かけるが、他の制御文字は許すこと
になってしまう
• 詳しくは、徳丸本4.2節で
Copyright © 2011 HASH Consulting Corp. 14
16. 安全なSQLライブラリの基準は
• 以下がポイント
– 静的プレースホルダが使えること
– 文字エンコーディング指定ができる
– 文字エンコーディングが正しく反映される(5C問題など)
– バックスラッシュのエスケープにDBの設定が反映される
• MySQL: NO_BACKSLASH_ESCAPES
バックスペースをエスケープしない
• PostgreSQL: standard_conforming_strings
• メンテナンスが継続されていること
• 「安全なSQLの呼び出し方」に詳しく説明
• 同書では、MDB2を推奨している
• PHP5.3.8以降では、ようやくPDOも使えるレベルになった
Copyright © 2011 HASH Consulting Corp. 16
21. htmlspecialcharの正しい使い方
• 第2引数はENT_QUOTESでなくても本当はよい
– ENT_QUOTESを使わないと脆弱性という人までいる(;´Д`)
– 要素内容は、どれを指定してもOK
– ダブルクオートで囲った属性値は、ENT_COMPATか
ENT_QUOTES
– シングルクォートで囲った属性値はENT_QUOTES
– 属性値はダブルクォートで囲むことにすれば、ENT_COMPATで
統一してもOK
– 参考:徳丸本P102
• 第3引数は文字エンコーディングを正しく指定すること
– 指定する文字エンコーディングはmbstring.internal_encoding
– 省略時はISO-8859-1 (Latin-1) / 5.4 からはUTF-8
• htmlentitiesの方が安全という説は間違い(根拠がない)
Copyright © 2011 HASH Consulting Corp. 21
22. XSS対策の基礎の基礎
• HTTPレスポンスヘッダに文字エンコーディングを指定する
– header('Content-Type: text/html; charset=UTF-8');
• 要素内容は前述のhtmlspecialcharsの使い方
• 属性値はhtmlspecialcharsでエスケープした値をダブル
クォートで囲む
• src属性などにURLを動的生成する場合は、スキームに注意
– http:// か https:// か / で始まることを確認
• JavaScriptのリテラルを動的生成することはとても危険
– イベントハンドラの場合は、JSエスケープした値をHTMLエスケープ
– script要素内はとてもめんどくさいので、やらない方が賢明
– どうしてもやりたいなら、「過剰エスケープ」(徳丸本P113)
– hiddenパラメータに書いてDOMで読むのが無難(徳丸本P114)
Copyright © 2011 HASH Consulting Corp. 22
24. ファイルアップロードの危険性
• アップロードしたファイルをPHPスクリプトとして実行される
– PHPに限らず、JSP、ASPなどのスクリプトとして解釈される可能性
– CGIは実行権限が必要なので、通常は成立しない
• 書き込みの際に権限を過剰に設定していると成立する可能性も
• アップロードしたファイルをHTMLと誤認させるXSS
– 不適切なContent-Type設定が主要因
– IEに注意。画像のマジックバイトのチェックは必須
• getimagesizeが便利 (徳丸本P278)
• IE8以上は X-Content-Type-Options: nosniff が有効
(徳丸本には間に合わず)
• 詳しくは徳丸本4.12 P258~
Copyright © 2011 HASH Consulting Corp. 24
25. PHP逆引きレシピのアップロードサンプル
if (strlen($_FILES['uploadfile']['name'][$i]) > 0) {
# 画像ファイルの拡張子を取得して判定します。
$imgType = $_FILES['uploadfile']['type'][$i];
$extension = '';
if ($imgType == 'image/gif') {
$extension = 'gif';
} else if ($imgType == 'image/png' || $imgType == 'image/x-png') {
$extension = 'png';
} else if ($imgType == 'image/jpeg' || $imgType == 'image/pjpeg') {
$extension = 'jpg';
} else if ($extension == '') {
$error .= '許可されていない拡張子です<br />';
}
# getimagesize()関数で画像かどうかの判定をします。
$checkImage = @getimagesize($_FILES['uploadfile']['tmp_name'][$i]);
if ($checkImage == FALSE) {
$error .= '画像ファイルをアップロードしてください<br />';
} else if ($imgType != $checkImage['mime']) {
$error .= '拡張子が異なります<br />';
} else if ($_FILES['uploadfile']['size'][$i] > 102400) {
# 画像ファイルのサイズ上限をチェックします。
$error .= 'ファイルサイズが大きすぎます。100KB以下にしてください<br />';
かなり良い } else if ($_FILES['uploadfile']['size'][$i] == 0) {
# 画像ファイルのサイズ下限をチェックします。
のだが惜し $error .= 'ファイルが存在しないか空のファイルです<br />';
} else if ($extension != 'gif' && $extension != 'jpg' && $extension != 'png') {
いところも # 画像ファイルの拡張子をチェックします。
$error .= 'アップロード可能なファイルはgif、jpgまたはpngのみです<br />';
} else {
# ここでは格納ディレクトリの下に「"upfile_" + 現在のタイムスタンプ + 連番 + 拡張
子」で配置します。
$moveTo = $filePath . '/upfile_' . time() . $i . '.' . $extension;
25
26. PHP逆引きレシピ・サンプルの残念なところ
• チェックが厳しすぎて、IE8以前で、PNG画像のアップロード
がエラーになる
– IE8まで、ブラウザが送信するMIMEはimage/x-png
– MIMEのチェック部分では考慮している
– getimagesizeが返すMIMEはimage/pngなのでエラーになる(;´Д`)
• ファイル名の生成に現在時刻(秒単位)を使っているので
ファイル名の衝突の可能性
– 同一時刻であれば、「遅いもの勝ち」となる
– 状況によっては脆弱性となる
• 画像管理ソフトで、Aさんは「恥ずかしい画像」を非公開ファイルとして投稿、
Bさんは、画像を公開ファイルとして投稿
• たまたま同一時刻なのでファイル名が同一となる
• Bさんの公開画像として、Aさんの「恥ずかしい画像」が公開
• Aさん、Bさんともに、恥ずかしいことに(;´Д`)
Copyright © 2011 HASH Consulting Corp. 26
27. PHP逆引きレシピ・サンプルの残念なところ
• チェックが厳しすぎて、IE8以前で、PNG画像のアップロード
がエラーになる
– IE8まで、ブラウザが送信するMIMEはimage/x-png
でも、全体としては、
– MIMEのチェック部分では考慮している
逆引きレシピはかなりいいよ
– getimagesizeが返すMIMEはimage/pngなのでエラーになる(;´Д`)
• ファイル名の生成に現在時刻(秒単位)を使っているので
ファイル名の衝突の可能性
– 同一時刻であれば、「遅いもの勝ち」となる
– 状況によっては脆弱性となる
• 画像管理ソフトで、Aさんは「恥ずかしい画像」を非公開ファイルとして投稿、
Bさんは、画像を公開ファイルとして投稿 ユニークなファイル名生成
• たまたま同一時刻なのでファイル名が同一となる の例は、徳丸本P266参照
• Bさんの公開画像として、Aさんの「恥ずかしい画像」が公開
• Aさん、Bさんともに、恥ずかしいことに(;´Д`)
Copyright © 2011 HASH Consulting Corp. 27
29. 文字コードのセキュリティまとめ
• 文字コードの基本的なところを勉強しよう(徳丸本6章)
– 文字集合:ASCII、ISO-8859-1、JIS X 0208、…Unicode
– 文字符号化形式:Shift_JIS、EUC-JP、UTF-8、UTF-16
• 文字コードを正しく扱う基本
– 入力:文字エンコーディングの妥当性チェック
– 処理:処理中に文字集合を変更しない
マルチバイト対応の関数を使う(mbstring系)
– 出力:文字コードの指定を正しく行う
• HTTPレスポンスヘッダの文字エンコーディング指定
• SQL接続時の文字エンコーディング指定
– 設定:php.iniの正しい文字コード設定
• 文字エンコーディングの妥当性チェックで防げない脆弱性に
注意
Copyright © 2011 HASH Consulting Corp. 29
33. クリックジャッキングの対策
• クリックジャッキングの影響はクロスサイト・リクエストフォー
ジェリ(CSRF)と同等
– ユーザの意識とは無関係に、ユーザの権限で操作が行われる
• クリックジャッキングされると困るページには、X-FRAME-
OPTIONSヘッダを指定する(徳丸本P63)
– frame/iframeを禁止して良い場合
– header('X-FRAME-OPTIONS', 'DENY');
– frame/iframeを禁止できないが単一ホストの場合
– header('X-FRAME-OPTIONS', 'SAMEORIGIN');
• CSRF対策のトークン発行しているページが対象となる
• メタ要素によるX-FRAME-OPTIONS指定は無効です。
徳丸本第3刷までの記述は間違いです(_ _)
Copyright © 2011 HASH Consulting Corp. 33
35. どうして暗号化ではなくてハッシュなの?
• 暗号化の場合、鍵の管理が難しい
• アプリケーションは鍵を使わなければならないが、攻撃者には鍵を見せ
たくない
• PSNの事件では、権限昇格されたことになっているので、暗号鍵も盗ま
れていると想定せざるを得ない
• ハッシュだと鍵を使わないので、鍵管理のわずらわしさがない
• パスワードをサイト管理者にも知られたくないというニーズも
– 暗号化されたパスワードだと、サイト管理者やヘルプデスク担当者がパスワードを
知り得るのが嫌だ
– ヘルプデスクに見せないようにするには、サポート用画面の機能次第で可
– 管理者の悪事は総合的な対策が必要で、パスワードの問題だけではない
• PCI-DSS2.0 8.4項には「8.4 強力な暗号化を使用して、すべてのシス
テムコンポーネントでの伝送および保存中のすべてのパスワードを読
み取り不能にする」とあり、ハッシュを求めてはいない
Copyright © 2011 HASH Consulting Corp. 35
36. ハッシュで保存されたパスワードは本当に安全なの?
• 一般的に、(暗号論的)ハッシュ値から平文を「復元する」ことはできな
い
– 「password」のMD5ハッシュ: 5f4dcc3b5aa765d61d8327deb882cf99
• しかし、パスワードの場合は特別な事情がある
• 例:4桁の暗証番号をハッシュ値で保存している場合
– 全ての可能性は1万通りしかないのだから、総当たりで確認すれば、平文の暗証
番号はすぐに判明する
• 原理は8桁パスワードでも同じ
• ハッシュ保存の場合、アルゴリズムは攻撃者が知っている前提で安全
な設計とする
– 平文パスワード以外は、すべて「ばれている」想定を置く
• 攻撃者にとって未知であることが保証された情報があれば、それを鍵と
して暗号化すればよい。現実にはそのような保証がないから暗号化を
用いない
Copyright © 2011 HASH Consulting Corp. 36
37. Saltってなに?
• ソルト(Salt)とは、ハッシュの元データ(パスワード)に追加する文字列
• 見かけのパスワードの長さを長くする
– 公開されたレインボーテーブルは10文字までのパスワードに対応しているので、
パスワードとソルトを合わせて20文字以上にしておけば、当面は大丈夫
• ユーザ毎にソルトを変えることで、パスワードが同じでも、異なるハッ
シュ値が得られる
• ソルトの要件
– ある程度の長さを確保すること
– ユーザ毎に異なるものにすること
• ソルトには乱数を用いることが多いが、乱数が必須というわけではない
(暗号論的に安全な乱数である必要はもちろんない)
• ソルトは秘密情報ではない。ソルトは、通常ハッシュ値と一緒に保存す
る
Copyright © 2011 HASH Consulting Corp. 37