mod_rewrite + Suhosin Extension (PHP) の組み合わせで構築されたサイトを Sleipnir でブラウズするとログイン状態が継続できない場合がある

mod_rewrite + Suhosin Extension (PHP) の組み合わせで構築されたサイトを Sleipnir でブラウズするとログイン状態が継続できない場合がある」

なにを言っているのか(ry

まあマジ話ですよ。アプリにとんでもない欠陥があるんじゃないかと本気で焦っちまったぜ。

現象は以下のような環境で発生します。

  • suhosin 拡張が有効になった PHP を使用している
  • suhosin.session.cryptua が on (デフォルト)
  • /favicon.ico でアクセスできるリソースが存在しない
  • mod_rewrite の設定で、存在しないファイルへのアクセスは PHP スクリプトに内部リダイレクトされる

というか有り体に言えば、suhosin 拡張を有効にして認証有りの symfony アプリを設置して favicon.ico を置かない状態にした上で Sleipnir でブラウズすれば発生すると思います。

なんでこんなことが?

OpenPNE 開発談義というスカイプチャットルームの俺の発言をそのまま引用します。

[09/02/20 19:47:34] 海老原昂輔 (Kousuke Ebihara): 簡単に言うと、
1. suhosin 拡張によってセッションがUAの値に基づいて作られる
2. Sleipnir でログインとかページ遷移とかする
3. Sleipnir が通常とは異なるUAで /favicon.ico に GET しようとする
4. だが /favicon.ico はない
5. mod_rewrite により、 /favicon.ico への GET が OpenPNE3 の PHP スクリプトに行く
6. セッション生成時のUA と /favicon.ico へのアクセス時の UA が違うため、 suhosin がセッションを無効化する
7. ログアウト

原因発覚にいたる経緯と解説

原因がまったく検討つかなかったんですが、限定環境でしか発生しないことから、なんとなく「Sleipnir のリクエストヘッダがおかしいんじゃ?」「Suhosin がなにかやらかしているんじゃ?」という予感はしていました。ということで開発環境の Suhosin を無効にしてみたら正常な挙動になった! やっぱり!

ということで設定項目をひとつひとつ無効にしてみたら suhosin.session.cryptua が on のときに現象が発生することが判明! ここまできたらもうこっちのものだ!

suhosin.session.cryptua が on だとどうなる?

そもそも suhosin ってなんだよって人は http://www.hardened-php.net/ 行ってください。

で、 suhosin.session.cryptua が何者かについては、(字面から想像つきますが)、 http://www.hardened-php.net/suhosin/configuration.html#suhosin.session.cryptua にて以下のように説明されています。

suhosin.session.cryptua
    * Type: Boolean
    * Default: On
Flag that decides if the transparent session encryption key depends on the User-Agent field. (When activated this feature transparently adds a little bit protection against session fixation/hijacking attacks)

ですよねー。

でもじゃあなんでこれが Off になってると正しく動くのさ? なんか変なことやってんじゃないの? ということでソースコードを追ってみました。(http://download.suhosin.org/suhosin-0.9.27.tgz から入手可能)

この設定が実質的に影響するのは session.c の以下の部分のみ。

397 char *suhosin_generate_key(char *key, zend_bool ua, zend_bool dr, long raddr, char *cryptkey TSRMLS_DC)
398 {
399     char *_ua = NULL;
400     char *_dr = NULL;
401     char *_ra = NULL;
402     suhosin_SHA256_CTX ctx;
403 
404     if (ua) {
405         _ua = sapi_getenv("HTTP_USER_AGENT", sizeof("HTTP_USER_AGENT")-1 TSRMLS_CC);
406     }

:

424     if (_ua) {
425         suhosin_SHA256Update(&ctx, (unsigned char*)_ua, strlen(_ua));
426     }

まあ特におかしなことをやっている様子はないと。じゃあやっぱりおかしいのは Sleipnir ではということに。

SleipnirUA 変わってるんじゃ……?

まあとにもかくにもリクエストヘッダを見てみようかと Sleipnir 界隈を漁ったんですが、 Firefox でいう LiveHttpHeader みたいなプラグインはないんですね。あれすごい便利なのに。

ということでパケットキャプチャソフト(Wireshark ってやつ)を落としてきて、そいつで解析しました。

リクエスト1

POST /member/login/authMode/MailAddress HTTP/1.1\r\n
:
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727) Sleipnir/2.8.2\r\n

リクエスト2

GET /favicon.ico HTTP/1.1\r\n
:
User-Agent: Sleipnir/2.8.2\r\n

おかーさーん! この子 UA 勝手に切り替えちゃってるよー!

なんでこんなことに……と泣きそうになりながらヘッダを洗っていくと、 UA が 「Sleipnir/2.8.2」 となるのは favicon.ico に対してのみ、ということに気づきました。いやしかしそれなら PHP スクリプトにはなんの関係もなかろう、なぜ suhosin の設定変えれば動くんだよ、とツッコミを入れながらレスポンスヘッダをよく見てみると、

HTTP/1.1 404 Not Found\r\n

アーッ!!! mod_rewrite

mod_rewrite を見てみる

見てみる。といっても symfony デフォルトのままだけどね。

<IfModule mod_rewrite.c>
  RewriteEngine On

  # uncomment the following line, if you are having trouble
  # getting no_script_name to work
  #RewriteBase /

  # we skip all files with .something
  #RewriteCond %{REQUEST_URI} \..+$
  #RewriteCond %{REQUEST_URI} !\.html$
  #RewriteRule .* - [L]

  # we check if the .html version is here (caching)
  RewriteRule ^$ index.html [QSA]
  RewriteRule ^([^.]+)$ $1.html [QSA]
  RewriteCond %{REQUEST_FILENAME} !-f

  # no, so we redirect to our front web controller
  RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

どう見ても index.php 叩きに行きます、本当にありがとうございました。

解決策

これもチャットのコピペですが、以下の解決策がありそう。

[09/02/20 19:49:10] 海老原昂輔 (Kousuke Ebihara): まあ対処方法としては、
[09/02/20 19:52:30] 海老原昂輔 (Kousuke Ebihara): A. セッション生成時に UA を使わないようにする
 a. suhosin 拡張自体を無効化
 b. suhosin.session.cryptua を off
B. /favicon.ico へのリクエストで PHP を叩かせないようにする
 a. favicon.ico を置く
 b. mod_rewrite などの設定を変更し favicon.ico へのリクエストが来ても PHP スクリプトに内部リダイレクトさせない
[09/02/20 19:53:14] 海老原昂輔 (Kousuke Ebihara): 4つのうちのどれかを選ぶ感じかなと

他にも以下が思いついた。