PHPのビルドインサーバで発生するapache_request_headers()のバグとそのworkaround

apache_request_headers()のバグ

このバグはPHP :: Bug #67594 :: Unable to access to apache_request_headers() elementsで報告されているものです。

内容としては、PHPをビルドインサーバ(php -S)で動かしている場合、apache_request_headers()を実行して返ってくる配列に要素に、その要素が存在するにもかかわらずアクセスできないというバグです。ただし、その配列をvar_dump()すると内容を見ることはできます。

例えば、var_dump(apache_request_headers())を実行した場合は、

array(8) {
  ["Host"]=>
  string(14) "localhost:8080"
  ["Connection"]=>
  string(10) "keep-alive"
  ...
}

となりますが、同じ環境でvar_dump(apache_request_headers()['Host'])を実行した場合は、

NULL

となってしまします。

対象バージョン

どのバージョンで発生するのかは詳しく調べていませんが、Mac OS X Yosemite(10.10)のPHP 5.5.14やUbuntu 14.04の5.5.9では発生しています。

どうやら、5.6.0からは修正されているみたいです。

Workaround

apache_request_headers()が返す配列の要素に直接アクセスすることはできませんが、foreach経由でならアクセスが出来ました。そのため、次のように新しい配列にforeachを使ってコピーすればこの問題を回避できます。

$requestHeaders = [];
foreach(apache_request_headers() as $key => $value){
  $requestHeaders[$key] = $value;
}

その他

apache経由でなくても使えるのにapache_request_headers()っていう名前、どうかしてる。

Nginx + PHP-FPMをPATH_INFOがきちんと使えるように設定する

はじめに

NginxでPHP-FPMを使う場合、多くの解説サイトでは

location ~ \.php$ {
  ...
}

と設定すると書かれています。Nginxのデフォルトの設定ファイルにもコメントでこの書き方が載っています。

しかしこの設定では、PHPでPATH_INFOを取得することができません。より正確に言えば、PATH_INFOを含むURLはPHPとして実行されません。
つまり、http://example.com/foo.php/bar/というURLは前述の設定では扱えません。

このようなURLを使わないのならば問題ないのですが、多くのフレームワークではPATH_INFOを使いますし、mod_phpで出来る事が出来ないのは許せませんよね。

NginxのWikiの設定例

http://wiki.nginx.org/PHPFcgiExample にPATH_INFOを扱える設定例が載っています。

location ~ [^/]\.php(/|$) {
  fastcgi_split_path_info ^(.+?\.php)(/.*)$;
  if (!-f $document_root$fastcgi_script_name) {
    return 404;
  }
  ...
}

この方法でPATH_INFOは扱えることには扱えるのですが、うまく動かない場合もあります。 以下のような場合です。

問題が生じる例

上記の設定例では、サーバ上のディレクトリ名に.phpが含まれる場合正常に動作しません。

例えばhttp://example.com/foo.php/bar.php/foobar.phpというURLの場合、foo.phpPHPファイルである場合は動きますが、ディレクトリ名であった場合は動きません。

PATH_INFOをきちんと扱える設定

どのようなURLでもきちんとPATH_INFOを扱うには、php.inicgi.fix_pathinfo=0とする必要があります。 従って、PHP 5.3.8以前ではこの設定を使うとセキュリティ上問題が発生するので、PHP 5.3.9以降を使えない方はNginxのWikiに記載されている設定を使いましょう。

次に示す設定なら大体のURLでPATH_INFOがうまく扱えるはずです。

location ~ [^/]\.php$ {
  try_files $uri =404;
  fastcgi_split_path_info ^(.+\.php)(/.*)$;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  include fastcgi_params;
  fastcgi_param PATH_INFO $fastcgi_path_info;
}

location ~ [^/]\.php/ {
  try_files $uri $uri/ @php-fpm;
}

location @php-fpm {
  fastcgi_split_path_info ^(.+\.php)(/.*)$;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  include fastcgi_params;
  fastcgi_param PATH_INFO $fastcgi_path_info;
}

もっといい書き方があったら教えてください。

PHP-FPMでcgi.fix_pathinfo=0は必要なのか

tr;dr

PHP 5.3.9以降なら必要ない。

はじめに

NginxでPHPを動かす場合、大抵PHP-FPMを使うと思います。その設定方法を説明するほとんどのサイトではphp.inicgi.fix_pathinfo=0と指定すべきとしています。

例えばHow To Install Linux, nginx, MySQL, PHP (LEMP) stack on Ubuntu 14.04 | DigitalOceanではデフォルトのcgi.fix_pathinfo=1が超危険と書かれています。

This is an extremely insecure setting because it tells PHP to attempt to execute the closest file it can find if a PHP file does not match exactly.

またUbuntuDebianのNginxの設定ファイル/etc/nginx/sites-available/defaultにも以下*1のように書かれており、やはりこの設定は必ずやらないといけないように思えます。

# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

なぜこの設定が必要なのかを調べてみました。

cgi.fix_pathinfo=0とするようになった経緯

cgi.fix_pathinfo=0とするようになったのはFastCGIを使った際に生じるセキュリティ上の問題*2が原因です。

例えば、ファイルのアップロードが可能なウェブサイトがあったとします。このサイトはアップロードされたファイルを

http://example.com/uploads/photo.jpg

のようなURLで公開します。アップロードできるファイルの拡張子.jpgなどに制限されています。ここで、

http://example.com/uploads/photo.jpg/foo.php

にアクセスがあった場合、FPMは/uploads/photo.jpg/というディレクトリ内のfoo.phpを実行しようとします。もちろんそのようなファイルはないので404が返るはずですが、cgi.fix_pathinfoが有効な場合(デフォルト有効)FPMは次に/uploads/photo.jpgPHPとして実行しようとします(/foo.phpはPATH_INFOとして扱われます)。

/uploads/にはだれでもファイルをアップロードできるので、拡張子をjpgとしたPHPファイルをアップロードされた場合はかなり問題です。

そこで、cgi.fix_pathinfoを無効にして前述の動作をさせないようにする対策が取られるようになりました。

security.limit_extensionsの導入

この問題はFPMが.php以外のファイルをPHPとして実行しないようにすれば発生しません。

PHP :: Request #55181 :: Enhance security by limiting the script extensionsecurity.limit_extensionsを導入してFPMが実行できる拡張子を制限することが提案され、2012年1月にリリースされたPHP 5.3.9からこの仕組みが導入されています。 security.limit_extensionsのデフォルト値は.phpです。したがって、PHP 5.3.9以降ではcgi.fix_pathinfoを無効にする必要はありません。

まとめ

PHP 5.3.9以降ではcgi.fix_pathinfo=0とする必要性はない。

例えば冒頭で紹介したサイトはUbuntu 14.04を対象としているのでcgi.fix_pathinfo=0とする必要性はない。

安全側に倒してcgi.fix_pathinfo=0としても問題ないが、cgi.fix_pathinfoを有効にすると嬉しい事があるので有効にする流れになればいいと思う。どう嬉しいのかは別エントリで述べる。

参考

#642995 - Security issue with Nginx + PHP via FastCGI - Debian Bug report logs

http://cnedelcu.blogspot.jp/2010/05/nginx-php-via-fastcgi-important.html

*1:ちなみにこの文言はhttp://nginx.org/で配布されているNginxには書かれていません。

*2:2010年5月にnginx文件类型错误解析漏洞 - 80secで報告されたものです。