Self-XSSでも盗めないHTTPセッションで可能か?
Self-XSSは簡単に言うと、ブラウザーの開発者モード(コンソール)をユーザー自身に操作させる攻撃。
場合によってはセッション情報を盗んで、別のところから改めて攻撃もできる。
根本的な対策は難しく、コンソール画面に警告を出しておくとかがあるらしい。
そのブラウザでのセッションの対策は難しくても
セッション情報を盗めないようにできないか?
ちょっと考えて、WebCrypto APIを使って鍵交換・暗号化することで出来そうと、思って色々やってみた。
一応できる
はい、一応です。
少なくともSNS等では到底使えない方法です。
全体の流れ
-
クライアント側で
subtle.generateKey()を使いECDH鍵ペアを作成
公開鍵をサーバーに送信 -
サーバーも同様にECDH鍵ペアを作成
クライアントから送られてきた公開鍵をsubtle.importKey()で取り込んで
共有情報を生成しsubtle.deriveKey()で共有秘密鍵を作成
そしてその情報と紐づけるセッションIDしてを生成し
セッションIDとサーバー側公開鍵をクライアントに返答 -
クライアント側は受け取ったサーバー側公開鍵と作成済みの秘密鍵を使って
同様に共有秘密鍵を作成 -
以降はHTTPヘッダにセッションIDを入れて、ボディ部は共有秘密鍵で暗号化
受け取り側は共有秘密鍵で複合
WebCryptoより外はブラウザなりnode.jsなりの実装で通常であれば中は見えない。
つまり灰色で塗った部分は簡単にはアクセス出来ないようになっている、
少なくとも、ネットワークだったりコンソールを含むJavaScriptからはアクセス出来ない。
見えるのは、お互いの公開鍵(PublicKey@C,PublicKey@S)とセッションID(SessionID)
暗号化に使う鍵(DeriveKey)とそれを生成するのに必要な秘密鍵(PrivateKey@C,PrivateKey@S)は
ネットワークはおろかJavaScript環境上も見えない
厳密には「JavaScript環境上も見えない」ために
generateKey(),deriveKey() でextractable=falseが必要
使用する鍵を特定するにもセッションIDは平文の必要があるので
セッションIDはHTTPレベルでは平文(TLSで保護)のままでJavaScriptコンソールでも見えますが
セッションIDが分かっても共有秘密鍵が分からないのでボディ部を解読できません。
実際に作っていろいろ気づいた
-
GETリクエストはセッションIDだけで出来てしまう
ボディ部しか暗号化しないので、ボディ部の無いGETは鍵が無くても通ります。
もっとも、レスポンスのボディ部は暗号化されているので読めませんので、
GETを偽装されても大きな問題にはなりません。 -
DELETEリクエストも保護できない場合がある
DELETEのボディ部はHTTP仕様上で禁止はされていませんが明確に使えるわけでもなく、
一部実装では使えない場合もあります。
この辺はクライアント・サーバーともにそう作ればいいけど、
ライブラリがダメだったりした場合は対応が難しい。 -
セッション保持が困難
見てもらう通りこの方法の最大の問題は、
交換した鍵がJavaScriptの実行インスタンス単位に制限されるので、
タブ間でも鍵の共有が出来ないし、同じタブでも再読み込みをしてしまうと鍵を失います。
結局のところ、これが実用上問題でした。
本当の目的
目的に関しては半分嘘で、当初の開発目的は内部ネットワーク内だけのシステムで
サーバーにTLS環境構築せず暗号化通信出来ないか?が始まり。
しかし、WebCrypto APIが安全なコンテキスト
でないと動かないという事で、この目的では使えないことが判明している。
でも、気づいたのはそれなりに形が出来て、
これに合わせたセッション管理になっている状態だったので
完全ではないものの、対策を入れて使っている。