More Related Content Similar to ウェブアプリのセキュリティをちゃんと知ろう (毎週のハンズオン勉強会の資料) (20) ウェブアプリのセキュリティをちゃんと知ろう (毎週のハンズオン勉強会の資料)10. ウェブサーバーを通したウェブブラウザからの入力の仕様を考えよう PHP に入ってくる値は何かを知る 可変長のバイト列 (文字列ではない!!) GET パラメータ POST パラメータ アップロードファイル リクエストヘッダ (Cookie など) 実際の処理に渡すべき値は何かを考える 文字列か、バイト列か?文字コードは何か? (ウェブサーバーでバイト列を処理することってあまりないので、 PHP では基本的に文字コードのバリデーションは必要だと思って良い) 長さはどうか? どういう文法や構造を持つデータ? 入力された値を実際の処理に渡すべき値かどうかを確認することを「バリデーション」という 11. GET パラメータのバリデーション # PHP に入ってくる可能性があるのは可変長のバイト列 $url = $_GET['url']; if (!mb_check_encoding($url, 'UTF-8')) throw new Exception('文字列ではない'); # この時点で $urlは UTF-8 でエンコーディングされた文字列ということが保証される $url_length = mb_strlen($url, 'UTF-8'); if ($url_length > 512) throw new Exception('文字列が長すぎる'); # この時点で $urlは UTF-8 でエンコーディングされた 512 文字以下の文字列ということが保証される if (!preg_match('/s?https?:[-_.!~*'()a-zA-Z0-9;?:@&=+$,%#]+/u', $url)) throw new Exception('URL として不正'); # この時点で $urlは Http URL であることが保証される $url_info = parse_url($url); if ($url_info['host'] !== 'ohma-inc.com') throw Exception('外部サイトの URL'); # この時点で $urlは ohma-inc.com の Http URL であることが保証される 12. アップロードファイルのバリデーション if (!$_FILES['file']) throw new Exception('ファイルがアップロードされなかった'); $file = $_FILE['file']; # この時点でmulipart/form-data によって file というパラメタ名で # ファイルが送信されたことが保証される if (!is_uploaded_file($file['tmp_name']) or $file['error'] !== 0) throw new Exception('アップロードエラー'); $filename = $file['tmp_name']; # この時点で $filename は HTTP_POST によって送信されたファイルを # 一時保存しているファイルのパスであり、 php.ini に設定された # アップロードファイルのファイルサイズ以内であることが保証される $info = getimagesize($filename); if (!$info or !isset($info['mime']) or $info['mime'] === 'image/gif') throw new Exception('アップロードされたファイルが GIF じゃない'); # この時点で $filename は HTTP_POST によって送信されたファイルを # 一時保存しているファイルのパスであり、 php.ini に設定された # アップロードファイルのファイルサイズ以内であり # GIF のマジックバイトを持つことが保証される 13. ウェブサーバーを通したウェブブラウザへの出力の仕様を考えよう ブラウザへ渡すべき値は何かを考える 文字列なのか、バイト列なのか?文字コードは何? (動的に画像を生成するような場合以外は、だいたい文字列を出力することが多いよね) 出力するデータの、文法やデータ構造は? (MIME タイプは何?) ブラウザが正しく文法やデータ構造、文字コードを理解し処理できるには何が必要? X-Content-Type-Options: nosniff を送ったうえで、Content-Type は正しく遅れているか 文法やデータ構造を守った文字列やバイト列を生成するにはどうしたらいいか 文法やデータ構造を正しく出力するための手法 シリアライズ、エスケープ テンプレートに埋め込む場合に重要なことは、文法をまたがらず、たった一つのリテラルのみを作ること XSS は、正しく HTML や JavaScript を生成出来ていない場合や、ブラウザに正しく Content-type や文字コードを伝えられていない場合などに発生する 14. php apache の設定 HTML に正しくコンテンツを認識させる header('Content-Type: text/html; charset=utf-8'); header('X-Content-Type-Options: nosniff'); header('Content-Type: application/json; charset=utf-8'); header('X-Content-Type-Options: nosniff'); AddDefaultCharset utf-8 Header set X-Content-Type-Options nosniff 15. 正しいデータを生成する1 ダメな例 例えば $data = "quot;><script>alert(1)</script><a href=quot;"; ... <a href="/search?q=<?= $data ?>"></a> ... 16. 正しいデータを生成する1 $data JS 文字列 URL Component CSS 識別子 JS 識別子 CDATA PCDATA PCDATA PCDATA RCDATA htmlspecialchars(rawurlencode($data), ENT_QUOTES, 'UTF-8') 17. 正しいデータを生成する1 正しい例 ... <a href="/search?q=<?= htmlspecialchars(rawurlencode($data), ENT_QUOTES, 'UTF-8') ?>"></a> ... 19. 正しいデータを生成する2 $data JS 文字列 URL Component CSS 識別子 JS 識別子 CDATA PCDATA PCDATA PCDATA RCDATA preg_replace('/</u', 'u003cu002f', json_encode($data)) 20. 正しい例 正しいデータを生成する2 ... <script> var data = <?= preg_replace('/</u', 'u003cu002f', json_encode($data)); ?>; if (typeof(data) !== 'string') throw Error('文字列じゃない!'); ... 22. 正しいデータを生成する3 $data JS 文字列 CSS 識別子 JS 識別子 CDATA PCDATA PCDATA PCDATA RCDATA htmlspecialchars(json_encode($data), ENT_QUOTES, 'UTF-8'); 23. 正しい例 正しいデータを生成する3 ... <a onclick="var data = <?= htmlspecialchars(json_encode($data), ENT_QUOTES, 'UTF-8'); ?>; if (typeof(data) !== 'string') throw Error('文字列じゃない!');... ... 26. ダメな例 (正しくない SQL が生成される可能性がある) 正しい例 ( "?" がプレースホルダ) 正しい SQL を生成する $db->execute('SELECT name FROM member WHERE member_id = "' . $member_id . '"'); $stmt= $db->prepare('SELECT name FROM member WHERE member_id = ?'); $stmt->bind_param($member_id); $stmt->execute(); 29. リクエスト元 リクエスト先 自サイトからリクエストされたことを保証する $token = base64_encode(openssl_random_pseudo_bytes(64)); setcookie('csrf_token', $token); ... <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token, ENT_QUOTES, 'UTF-8'); ?>"> ... if ($_POST['csrf_token'] !== $_COOKIE['csrf_token']) throw new Exception('想定外');