JWTが凄い使われ方をしているようなので、可能な限りマシなCookieベースのセッションセーブハンドラーを書きました。メリットは
- サーバー側のリソース(DBやファイルなど)を使わないので簡単にスケールする
ことにあります。
しかし、Cookieの保存は大きく分けて3つの問題があります。
- ネットワークが不安定だとデータが失われる場合がある(ネットワーク接続の問題)
- ロックがないのでデータが失われる場合がある(サーバー側の問題)
- 更新のタイミングによりデータが失われる場合がある(クライアント側の問題)
※ この問題はこのCookieを利用したセーブハンドラの問題ではなく、クッキー等のクライアント側にセッションデータを持たせるセッション管理の仕組み全般に存在します。
このハンドラーはデータ改ざんを検出するようになっており、Cookieの問題により改ざん攻撃と誤検出する可能性があります。
1、2は一般に広く認知されている問題でしょう。この場合はほぼ攻撃と誤検出されないと思われます。攻撃ではないのに攻撃と誤検出するのはほぼ3のケースだと考えられます。壊れ方によっては3ケースでも攻撃とは検出せず、仕様として単純にクッキーをリセットする場合もあります。タイミングによるデータ損失が100%攻撃と誤検出されるのではありません。
調べたのはしばらく前になりますが、タイミングによって失われるケースも完全に無視してよい程度ではありませんでした。これはブラウザのクッキー保存の実装コードの問題と考えられます。初期のバージョンはこの問題に対する緩和対策をしたコードにしていました。今は対タンパー性を上げる為にほぼ完全にバリデーションするコードになっています。(詳しくはコメント欄を参照)
どちらも一長一短で何とも言えないです。更新タイミングによるデータ損失が、どのブラウザで起きるのか、どのような使い方をすると起きるのか、どの程度あるのか、分らないので攻撃と誤検出するケースがある場合には教えていただけると助かります。※
※ 現コードはHMACで使用するクッキーをバリデーションしているので、更新タイミングによるデータ損失があると攻撃と誤検出します。きっちり正しく動作するコード ≠ きっちり厳格にバリデーションするコード、となる数少ないタイプのコードになります。ブラウザがRDBMSのトランザクションのように保存すれば済む話ですが。
試用方法
Githubリポジトリ: https://github.com/yohgaki/php-session-cookie-handler
git clone https://github.com/yohgaki/php-session-cookie-handler.git cd https://github.com/yohgaki/php-session-cookie-handler php -S 127.0.0.1:8888
としてからブラウザで http://127.0.0.1:8888/example.php にアクセスします。簡単なカウンターが表示されます。
PHP 7.1以上、opensslモジュールが必要です。
使い方
<?php
require_once __DIR__.'/php-session-cookie-handler.php';
$key = 'super secret master key'; // Or set $_ENV['PHP_SESSION_COOKIE_KEY']
session_set_save_handler(new SessionCookie($key));
session_start();
鍵は必須です。php -r ‘echo bin2hex(random_bytes(32));’ などとして安全かつ強力な鍵を作り使ってください。
セキュリティと制限
クライアント側にセッションデータを持たせるメリットはサーバー側のリソースを使わない事です。しかし、デメリットは小さくありません。
- クライアントに送ったセッションデータは無効化できない。(クライアント側任せなので本気でキープしようと思ったら可能)
- 無効化できないので、仮にWebアプリで不正利用と思われるアクセスに気付いたとしても、セッションの無効化が出来ない。(マスターキーを変えて全てのセッションデータの無効化は可能)
- 暗号化していてもオフラインで解析が何時まででも可能。
- Cookieにはロック機構がない。つまりセッションデータがロックされず、レースコンディションが発生する。(更新が失われる、といった事が発生する)
- Cookieなのでセッションデータは最大でも4KBまで。この実装の場合は他のクッキーなども考慮し最大2KBまでに制限。
基本的にセキュリティ要求が緩いアプリケーションでの使用のみに留めることをお勧めします。これは他のクライアントサイドにセッションデータを保存する仕組み全般に言えます。
色々問題がある、とは言ってもできる限りマトモな動作になるようにしています。実装しているセキュリティ対策には
- AES256による暗号化(完全にランダムなIV)
- HKDFの導出鍵による鍵生成(完全にランダムなsalt)
- HMACによる暗号データ/暗号キー/タイムスタンプ等の改ざんの検出
- セッション開始時間およびセッション再暗号化時間を利用した鍵生成
- 60秒毎の新しい暗号鍵の利用
- session.gc_maxlifetimeを利用した有効期限管理
といった対策が取られています。
単純にセッションデータを暗号化し、セッション管理へ使う場合とは比較にならないほど安全です。とは言っても、サーバー側でセッションデータを管理する方がより安全な管理が可能になります。
残念なのはデフォルトのPHPのセッションモジュールの機能では、タイムスタンプ管理をしていないので、このCookieベースのセッション管理よりも脆弱な仕様となっている部分があることです。これらを強化することは、このCookieベースのセッション管理のタイムスタンプ管理と似たようなことをすれば可能です。
繰り返しますが、クライアント側にセッションデータを置くセッション管理システムはあまり強いセキュリティが必要ないサービスに使ってください。