sessionハイジャックは広範囲に存在する比較的重大な脆弱性です。session技術において、クライアントサイドとサーバサイドはsessionのIDによってセッションを維持します。しかしこのIDは簡単にスニッフィングされ、第三者に利用されてしまいます。これは中間者攻撃の一種です。
本章ではセッションハイジャックの実例をお見せします。この実例を通して、読者がよりsessionの本質への理解を深めていただけることを願っています。
下のようなcountカウンターを書きます:
func count(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
ct := sess.Get("countnum")
if ct == nil {
sess.Set("countnum", 1)
} else {
sess.Set("countnum", (ct.(int) + 1))
}
t, _ := template.ParseFiles("count.gtpl")
w.Header().Set("Content-Type", "text/html")
t.Execute(w, sess.Get("countnum"))
}
count.gtplのコードは以下の通り:
Hi. Now count:{{.}}
ブラウザ上でリロードを行うと以下のような内容が確認できます:
図6.4 ブラウザでcount数を表示
リロードによって、数字は際限なく増加します。数字が6を示した時ブラウザ(ここではchromeを例にとります)のcookieマネージャを開くと、以下のような情報を見ることができます:
図6.5 ブラウザが保存しているcookieを取得
次のステップが重要です:別のブラウザ(ここではfirefoxブラウザを開きました)を開き、chromeのアドレスバーのアドレスを新たに開いたブラウザのアドレスバーにコピーします。その後firefoxのcookieエミュレートプラグインを開き、新規にcookieを作成します。上の図のcookieの内容をそのままfirefoxの中に再設定します:
図6.6 cookieをエミュレート
エンターキーを押すと、下のような内容が現れます:
図6.7 sessionのハイジャックに成功
ブラウザを変えても、sessionIDを取得することができました。この後cookieの保存過程をエミュレートします。この例は一台のコンピュータの上で行ったものです。たとえ二台によって行ったとしても結果は同じです。この時もし交代で2つのブラウザのリンクをクリックした場合、操作しているカウンターが実は同じものであるということに気づくでしょう。驚くことはありません。ここではfirefoxがchromeとgoserver間のセッション維持の鍵を盗みました。すなわち、gosessionidです。これは"セッションハイジャック"の一種です。goserverからすると、httpリクエストからgosessionidを得ました。HTTPプロトコルのステートレスによってgosessionidがchromeから"ハイジャック"されたものなんか知る方法はありません。依然として対応するsessionを探し、関連する計算を実行します。同時にchromeも自分が保持しているセッションがすでに"ハイジャック"されたことを知る方法もありません。
上のセッションハイジャックの簡単な例で、sessionが他の人にハイジャックされると非常に危険だとわかりました。ハイジャック側はハイジャックされた側を装い多くの非合法な操作を行うことができます。ではどのように効果的にsessionハイジャックを防止するのでしょうか?
ひとつの方法はsessionIDの値にcookieによってのみ設定されるようにすることです。URLの書き直し方法は許さないようにし、同時にcookieのhttponlyをtrueに設定します。このプロパティはクライアントサイドのスクリプトが設定されたcookieにアクセスできるか否かを設定します。まず、これによってこのcookieがXSSによって読み取られ、sessionハイジャックを引き起こすことを防止できます。つぎにcookieの設定がURLの書き直し方法によって容易にsessionIDを取得することができなくなります。
ステップ2は各リクエストの中にtokenを追加することです。前の章で述べたformの重複送信を防止するのに似た機能を実装します。各リクエストの中で隠されたtokenを追加し、毎回このtokenを検証することでユーザのリクエストがユニークであることを保証します。
h := md5.New()
salt:="astaxie%^7&8888"
io.WriteString(h,salt+time.Now().String())
token:=fmt.Sprintf("%x",h.Sum(nil))
if r.Form["token"]!=token{
//ログイン画面を表示
}
sess.Set("token",token)
もう一つの方法は、sessionの他に作成時間を設けることです。一定の時間が過ぎると、このsessionIDは破棄され、再度新しいsessionが生成されます。このようにすることで、ある程度sessionハイジャックの問題を防ぐことができます。
createtime := sess.Get("createtime")
if createtime == nil {
sess.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
globalSessions.SessionDestroy(w, r)
sess = globalSessions.SessionStart(w, r)
}
sessionが始まると、生成されたsessionIDの時間を記録する一つの値が設定されます。毎回のリクエストが有効期限(ここでは60秒と設定しています)を超えていないか判断し、定期的に新しいIDを生成します。これにより攻撃者は有効なsessionIDを取得する機会を大きく失います。
上の2つの手段を組み合わせると実践においてsessionハイジャックのリスクを取り除くことができます。sessionIDを頻繁に変えると攻撃者に有効なsessionIDを取得する機会を失わせます。sessionIDはcookieの中でやりとりされ、httponlyを設定されるため、URLに基づいた攻撃の可能性はゼロです。同時にXSSによるsessionIDの取得も不可能です。最後にMaxAge=0を設定します。これによりsession cookieがブラウザのログの中に記録されなくなります。
- 目次
- 前へ: sessionストレージ
- 次へ: まとめ