以前、secureクッキーの上書きに成功したことがある、というタレコミをいただいたので、HTTPSで追試してみました。
実験
というわけでHTTPSで実験してみます。適当に証明書をつくっておいてください。
同一URLでのHTTPからHTTPSへの上書きを試みる(secureなし)
foo.example.com用のサーバー証明書と秘密鍵を作っておいて、それぞれexample.crt、example.keyという名前でローカルに保存しておきます。実験では間違ってexample.com用のを作ってしまって警告が出た(鍵を作る時のオレオレルートCAはキーチェーンに入れていたのだけどCNを間違った)けど、まあ結果には影響はないでしょう。同じハンドラをhttpとhttpsの両方で使います。httpsでつながれた時(プロトコルがHTTP/2かどうかで判定)はsecret、そうじゃないときはevilという値をクッキーに設定します。
package main
import (
"fmt"
"net/http"
"net/http/httputil"
)
func main() {
var httpServer http.Server
var httpsServer http.Server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
dump, err := httputil.DumpRequest(r, true)
if err != nil {
http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
return
}
fmt.Println(string(dump))
if r.ProtoMajor == 2 {
http.SetCookie(w, &http.Cookie{Name: "test-cookie", Value: "secret", Path: "/", Domain: "example.com", MaxAge: 1000})
} else {
http.SetCookie(w, &http.Cookie{Name: "test-cookie", Value: "evil", Path: "/", Domain: "example.com", MaxAge: 1000})
}
fmt.Fprintf(w, "<html><body>hello</body></html>\n")
})
fmt.Println("start http listening :80 and :443")
httpServer.Addr = ":80"
httpsServer.Addr = ":443"
go func() {
fmt.Println(httpsServer.ListenAndServeTLS("example.crt", "example.key"))
}()
fmt.Println(httpServer.ListenAndServe())
}
期待する動作は次の通り。
-
https://foo.example.com
にアクセス(test-cookieというキーがsecretになる) -
http://foo.example.com
にアクセス(test-cookieというキーがevilになる) -
https://foo.example.com
にアクセスしたときに、クッキーの値(httpで設定したevilがサーバーに送信される)
実験してみると、見事にevilになりました!
クロスドメインはダメでしたが、同一URLならHTTPからsecureなしのHTTPSのクッキーの上書きはできました。
同一URLでのHTTPからHTTPSへの上書きを試みる(secureあり)
実は↑の設定は本来つけるはずのsecureを付け忘れて実験してしまっていたので、secureつきでも実験してみます。
次にクッキー設定のところに Secure: true
を追加するだけです。
http.SetCookie(w, &http.Cookie{Name: "test-cookie", Value: "secret", Path: "/", Domain: "example.com", MaxAge: 1000, Secure: true})
期待する動作は次の通り。
-
https://foo.example.com
にアクセス(test-cookieというキーがsecretになる) -
http://foo.example.com
にアクセス(test-cookieというキーがevilになる) -
https://foo.example.com
にアクセスしたときに、クッキーの値(httpで設定したevilがサーバーに送信される)
で、動かしてみると、evilにならず。元のクッキーにsecureが付与されていると上書きが無視されるようです。開発者ツールで見てもクッキーの値が変わらず。設定できた上で送信時にフィルタリングされるわけではなく、設定自体が無視されているようなので、httpで何度繋いでも送信したクッキーが返ってこないという。なかなかおもしろいですね。
というわけで、secureさえ付けたらクッキーインジェクションは怖く無さそうです。
追記(3/21): この仕様がRFC化されつつあるらしい。 http://qiita.com/flano_yuki/items/b87b2c28db0b056665ef#deprecate-modification-of-secure-cookies-from-non-secure-origins
まとめ
ブラウザベンダーすっごーーい!君はセキュリティが得意なフレンズなんだね!