Web StorageやindexedDBを扱う上でのセキュリティ上の注意点

localStorageやsessionStorage、あるいはindexedDBのようなブラウザ上でのデータの保存が可能になったことで、これらを取り扱ううえでもセキュリティ上の注意点が必要である。
これらのストレージは、localStorageやindexedDBは永続的に、sessionStorageはブラウザやタブを閉じるまでの間データが保持され続けるので、例えばWebアプリケーションがログイン機構を持っている場合にログイン中にこれらのストレージに書き込まれたデータは、ログアウト後も当然参照および書き換えが可能である。Webアプリケーション上のアカウントに紐づいたデータをこれらのストレージに書き込んでいる場合、ログアウト後もアクセス可能なことが問題を引き起こす可能性がある。
例えばTwitterのようなサービスがあったとして、(navigator.onLineプロパティなどを利用して)オフライン時にはユーザの発言をサーバにPOSTするのではなくlocalStorageに一時的に保存し、回線がオンラインに回復したときにlocalStorageの内容をサーバに改めてPOSTするような作り方をしていたとする。ここで、以下のような流れを考える。

  1. ユーザXとしてサービスにログイン
  2. 回線がオフラインになる
  3. 何か発言をポストするものの、オフラインなのでlocalStorageに保存される
  4. 回線がオンラインに回復する
  5. ログアウトする(localStorageには発言データが残ったまま!)
  6. 異なるユーザYとしてサービスにログイン
  7. localStorage内のデータがユーザYとしてサーバにPOSTされる

このように、本来であればユーザXとしての発言内容がユーザYの発言としてサーバに送信されてしまう可能性がある。
あるいは、機密情報を含むようなユーザ固有の設定値をlocalStorageにキャッシュとして保存していたとして、localStorage内にキャッシュがあればそれを優先して使用するという設計の場合には、設定画面で異なるユーザの機密情報が表示されてしまったり、あるいは他のユーザの設定に従った挙動をしてしまうといったような可能性も考えられる。
このような問題を起こさないためには、ストレージにユーザに紐づくデータを保存する際には、現在のユーザを識別する値を同時に保存しておき、保存された値を使用する際にはユーザが一致していることを確認する、という対応が不可欠である。

// 変数 uid はログイン中のユーザを識別する値
// uid が異なる場合にはストレージをクリア
if( localStorage.getItem( "uid" ) !== uid ){
    localStorage.removeItem( "tweet" );
    localStorage.setItem( "uid", uid );
}
...
// オフライン時に保存されているtweetを投稿
if( navigator.onLine && text = localStorage.getItem( "tweet" ) ){
    post_tweet( text ); // サーバへ送信
    localStorage.removeItem( "tweet" );
}
...
// オフライン時にtweetを一時保存
if( !navigator.onLine ){
    localStorage.setItem( "tweet", text );
}

ユーザ固有の値をストレージから削除するのがもったいないという場合は、(容量さえ気にならないのであれば)ユーザ識別子とともに保存すればよい。

// 変数 uid はログイン中のユーザを識別する値
// オフライン時に保存されているtweetを投稿
if( navigator.onLine && text = localStorage.getItem( uid + "-tweet" ) ){
    post_tweet( text ); // サーバへ送信
    localStorage.removeItem( uid + "-tweet" );
}
...
// オフライン時にtweetを一時保存
if( !navigator.onLine ){
    localStorage.setItem( uid + "-tweet", text );
}

…というようなことを、kazuhoさん、hidekさん、yappoさん、tokuhiromさんと話したのだけど、果たしてこれがベストプラクティスなのかどうなのかよくわからない。HTML5の機能に関しては、表面的な使い方だけであればあちこちに転がっているが、このように少し突っ込んだ話になるとまだまだ議論が必要というのが個人的な認識である。

(追記)ログイン時/ログアウト時に消すというのは、未ログイン状態とログイン状態でストレージ内容の共有を心配しなくていい場合や、サーバ側のタイムアウトによるログアウトなどが発生しないというのであればいいけど、たいていそういうことはないと思うので、そういう方法は取りにくい。

続き → Web StorageやindexedDBを扱う上でのセキュリティ上の注意点(続編) - 葉っぱ日記