第6回 ブラウザプログラミング (セッション)

第 6 回の目次
前回: JavaScript その 2
今回:
Web アプリにおけるセッション
Web アプリの脆弱性
HTTPS
【参考書】
・徳丸: 安全な Web アプリケーションの作り方
・高木: 安全な Web アプリ開発の鉄則, Internet Week チュートリ
アル
1 / 23
本日のソースコード
今回はなし
2 / 23
Web アプリケーションとセッション
Web アプリ: 種々の方法でセッションを作る
HTTP: メッセージベース (セッションなし)
TCP: セッションあり
IP: パケット通信 (セッションなし)
Web アプリにおいては,
一つのアプリは状態遷移で構成される
各ページは一つの状態に相当する
その一連の状態遷移をセッションと呼ぶ
一般の脆弱性対策に加え,
「セッションに関する脆弱性対策」
が必要
3 / 23
セッションの実装方法
Basic 認証でログイン
毎回ユーザ名とパスワードが (base64 で) 送信される.サーバ側で
紐付ければセッションを作れる (同時アクセス不可).
Digest 認証でログイン
md5 でハッシュ化して送る.Basic 認証と同様.
ハッシュ値を盗まれれば,セッションハイジャックは可能.
ハッシュ化のタネ (nonce) を毎回サーバーが変えればかなり安全.
hidden パラメタ
次のページに進むためのボタンの中に,
<input type="hidden" name="sessionid" value="NNNNN">
と入れておく.POST の中に,sessionid=NNNNN が入る.
Cookie
最初にログインした時に,サーバからブラウザへ
Set-Cookie: sessionid=NNNNN
と送る.以降ブラウザは毎回 sessionid を送ってくる.
4 / 23
Web アプリにおける脆弱性の例
セッションハイジャック
XSS (Cross Site Scripting)
CSRF (Cross Site Request Forgery)
5 / 23
セッションハイジャック
危険なセッション実装法の例
URL のクエリにユーザ名→最悪
http://..../?user=yamazaki
クエリにセッション ID →盗まれたら終わり
http://..../?sessionid=NNNNN
Referer から漏洩.URL に入れては駄目.
セッション ID が固定値,または推測しやすい
hidden → POST のボディなので Referer 漏洩は防げる
パケット盗聴には対しては同じ弱さ
Cookie → POST と同じ
Cookie はしょせんヘッダの一部なので,簡単に偽装できる.
ユーザ名にハッシュ(MD5) をかけても駄目.MD5っぽいこ
とは見ると分かるので,後は盗みたいユーザ名を MD5 にか
けるだけでよい.
6 / 23
安全なセッションの実現方法
Cookie はブラウザに残るため盗まれることを前提に設計する
Cookie にパスワードをそのまま入れない.例えば,ユーザ
名+パスワードをハッシュして入れる.
まあまあの方法:
毎回セッション ID を変更する.
セッションからログアウトしてしまえば盗まれても構わない.
ログイン中のセッションハイジャックの可能性は残る
セッション ID にまともな乱数を使って推測困難にする
ただしセッション ID,ユーザ,認可をきちんと紐付ける
Set-Cookie: secure
cookie 値が https 通信でしか送れないことを強制
中途半端に考えるより,フレームワーク (rails 等) を使った
方が安全
7 / 23
XSS (Cross Site Scripting)
予期せぬスクリプトをブラウザ上で動作させる脆弱性
動的にページを生成するサイトに,そのようなスクリプトを生成
させる
その結果:
・Cookie が盗まれる
・嘘の画面が表示される
・JavaScript 用 API を悪用
動的プログラム生成では,常に同種の脆弱性が生じうる
→ SQL インジェクション (後の回で)
8 / 23
XSS の例
http:/.../?comment=good
としてコメントを投稿するサーバがあったとする.
サーバ側のコード (JSP):
<% String comment=request.getParameter("comment"); %>
<div> <%= comment =%> </div>
悪意のある者が次のように投稿:
http:/.../?comment=<script src="http:/.../">
次のようなページが生成される:
<div> <script src="http:/.../"> </div>
このページを見たら上記の script が実行される.
対策は,外部入力が入り込むルートによる.
HTML を動的生成する時にメタ文字が入らないようにする
DOM を経由するパターンでは,適切なライブラリを使う
9 / 23
CSRF (Cross Site Request Forgery)
脆弱性のあるサイトにおいて利用者がある操作をしたかのように
見せかける
Session Riding とも言う
その結果:
・商品購入
・掲示板への書き込み
・パスワードの変更
サイトが元々提供していないサービス,許していない操作はでき
ない
10 / 23
CSRF の例
脆弱性のあるサイト A:
次の GET でコメントを投稿できる
http://サイト A/?comment=good
cookie で認証をしている
罠のサイト B:
あるページに次のようなリンクを入れておく
<a href="http:/.../?comment=bad">
→ クリックしたら bad というコメントが投稿.
JavaScript を使えば POST も可能.onload で自動投稿も可能.
セッションがハイジャックされているわけではない
Cookie が有効な時間帯を利用しているだけ
11 / 23
CSRF の対策
全体の作り方から考えないと駄目
まず守りたい操作 (大概は POST を含むはず) を決める
次のいずれかを行う
POST の hidden にセッション ID や秘密トークンを埋め込む
(ただし,hidden だって偽造はできる.)
利用者にパスワードを再入力させる
(OK ボタンを表示する程度では駄目)
12 / 23
参考: JSON ハイジャック
悪意あるページを次のようにする.
—–
JavaScript の定義をいじる.onerror ハンドラとか setter とか.
<script src=...>と書く.
—–
利用者がそのページを参照する.
script タグにより src へアクセス(正しいクッキーが付く)
返事が JSON で返ってくる(JSONP ではなく)
JS がパースをする
setter がいじってあれば,代入時に値が見える.
エラーを起こさせて onerror で見る手もある.
13 / 23
公開鍵暗号の基本
一方向性関数: <f,g>の組.f(g(x))=x.
[公開鍵暗号]
A
1 公開鍵 g と秘密鍵 f の生成
2 公開鍵 g の公開
B
3 公開鍵 g で文書を暗号化
4 送信 (←)
5 秘密鍵 f で復号化
[電子署名 (身元確認,改竄防止)]
A
B
1 公開鍵 f と秘密鍵 g の生成
2 公開鍵 f の公開
3 秘密鍵 g で本文を暗号化=署名
4 本文+署名を送信 (→)
5 公開鍵 f で署名を復号化
6 本文と一致か判定
14 / 23
通信内容の秘匿化: HTTPS
HTTPS = HTTP over TLS (Transport Layer Security)
HTTP
-----TLS
-----TCP
普通の HTTP
identify したサーバとの間の安全なコネクション
普通の TCP
相手から利用者への通信内容 (Web ページ) が改竄されていない
(http での GET は改竄の可能性あり.秘密入力の手前から
https を)
利用者から相手への通信内容が外部に洩れない
本当に通信したい相手なのかの保証ではない
CA は相手の社会的信用度等を評価しているわけではない
利用者がサーバの identification を見るしかない
15 / 23
TLS の特徴
安全な相互認証
安全なネゴシエーション
プライベート (他人に見られない) で信頼 (偽造できない) で
きるコネクション
汎用的なトランスポートとして使える
セッションの再接続を高速化するためのセッション ID の
キャッシング
16 / 23
TLS プロトコルの概要
RFC 2246 (v1.0), 4346 (v1.1), 5246 (v1.2)
1
暗号強度の決定
サーバ/クライアント双方で使用できる暗号の強度を決定
2
サーバの身元の確認
クライアントは,サーバからサーバ証明書 (= CA が電子署
名したもの) を受け取り身元を確認する.
3
暗号化通信の準備
クライアントが共有鍵を生成し,サーバ証明書に含まれてい
たサーバ公開鍵で暗号化して,サーバに送信
4
HTTP ベースの通信の開始
以後は,この共有鍵で暗号化されたコネクションで通信する
17 / 23
典型的なフロー (オプション多数あり)
クライアント
ClientHello
ClientKeyExchange
ChangeCipherSpec
Finished
Application Data
サーバ
-------->
<-------<-------<--------------->
-------->
-------->
<-------<-------<------->
ServerHello
Certificate
ServerHelloDone
ChangeCipherSpec
Finished
Application Data
ClientHello: 可能な暗号・圧縮方式の一覧,Session ID
ServerHello: 受理可能なアルゴリズム,Session ID
Certificate: サーバ証明書と公開鍵
ClientKeyExchange: プリマスターシークレット (乱数)
ChangeCipherSpec: 暗号化への移行を示す
Finished: 今までの通信内容を暗号化して送信 (双方で確認)
18 / 23
キャッシュが効いたときのフロー
クライアント
ClientHello
ChangeCipherSpec
Finished
Application Data
サーバ
-------->
<-------<-------<--------------->
-------->
<------->
ServerHello
ChangeCipherSpec
Finished
Application Data
Session ID を ClientHello,ServerHello で指定する
双方が Session ID の再利用に合意することが必要
合意したら継続再開となるため高速
Session ID の生存時間は最大でも 24 時間
キャッシュが効いたとしても http 並みとはならない
19 / 23
POODLE(Padding Oracle On Downgraded Legacy Encryption) 脆弱性
SSLv3 で発見.
前提:
1 平文をブロック P1 ...Pn に分けて暗号ブロック C1 ...Cn を生成
2 最後に Padding 長+Padding が付く
3 サーバでの復号方法: Pi =Decrypt(Ci )
⊕
Ci−1
攻撃方法:
1 最後のブロック Cn が Padding 長+Padding だけになるように調整
2 解読したい部分のブロック Ci をコピーして最後のブロック Cn に上書き
3 Pn =Decrypt(Ci )
⊕
Pn−1 をサーバが実行
4 Pn の第 1 バイトは Padding 長.何回もやると一致.
(確率 1/256)
5 一致すればサーバ処理が続くので観測すれば分かる.
6 Decrypt(Ci ) = Pn
7 Pi =Decript(Ci )
⊕
⊕
Pn−1 [ただし第 1 バイトの部分だけ] を入手!
Pi−1 [ただし第 1 バイトの部分だけ] が分かる!!
8 Pi の第 1 バイトに来る文字を他をいじって調節すればよい
20 / 23
HTTPS 化の問題と高速化手法
問題 1:TCP 3-way handshake + TLS 3-way handshake
問題 2:https になるとキャッシュが効かなくなる.必ずエンド-エンド.
→ サーバー重い.利用者には遅延.CSP はやりたくない.
改善策:
Session Resumption → 前のスライド (Session Cache)
Session Tickets: クライアント側にチケットを保存.サーバーは
Stateless.
OCSP (Online Certificate Status Protocol) Stapling:
証明書失効確認をサーバーが行い結果を付ける(従来はブラウザ
が確認).
False Start: データを一つ前のパケットから送り始める (→今は
ALPN?)
21 / 23
最近の話題
EV 証明書 (Extended Validation):
指針を満たしたサイトにのみ発行される証明書(ブラウザの
表示で分かる)
https デフォルト化
HSTS (HTTP Strict Transport Security):
次の接続からは HTTPS 接続を強制
Upgrade Insecure Requests:
ページ内の http リソースを全部 https にする(コンテンツの
書き換え不要)
Opportunistic Security for HTTP:
HTTP で暗号化だけを利用する(積極的攻撃には無力)
Let’s Encrypt: 証明書の取得を容易化する組織.
22 / 23
参考: HSTS を使った super cookie
あるページに次のようなリソースを入れる.
sub_domain00.domain/
sub_domain01.domain/
sub_domain02.domain/
...
sub_domain1F.domain/
これで 32 回のアクセスが起きる.
サーバーは全部 HTTP だったら,32bit の数字を生成する.
そのビットが 1 の sub domaini だけ,HSTS を使って https にセット.
次回は 32 回のアクセスから,http → 0,https → 1 として数値を復元.
23 / 23