Wednesday, January 23, 2013

Apache + PHP-FPM の chroot について

先日の 記事 (Jan 20, 2013) を書いた段階からまだほとんど PHP のことは勉強していませんが, ひとつだけ気になっていた点があったので, そこだけ少し手をつけてみました. すなわち, PHP スクリプトから, ファイルシステム上の任意のディレクトリやファイルなどに勝手にアクセスすることができないようにするには, どうしたらよいだろうかということです.

要するに, chroot ということなのですが, php-fpm.conf の中に chroot という設定項目がありましたので, これを使うことにします.

chroot してみます

たとえば, Apache 側では, 次のように設定しておきます.

    DocumentRoot /opt/www
    
    <Location /php/>
        Require all denied
    </Location>
    
    <LocationMatch ^/(.*\.php)$>
        ProxyPassMatch fcgi:127.0.0.1:9000/$1
    </LocationMatch>

(正直なところ, LocationMatch の引数を, なぜこのような正規表現にすればうまくいくのか, 実はよくわかっていません. 自分なりに考えて試してみた案はどれもいまいちうまくいかず, とりあえずは PHP-FPM - Httpd Wiki というページにあった例をそのまま使わせていただくことにしました.)

また, php-fpm.conf では, 次のように設定しておきます.

    chroot = /opt/www/php

そして, /opt/www/php には何か適当なスクリプトを置きます. たとえば, hello.php という名前で, 次のような内容にしておきましょうか.

    <?php
        header('Content-Type: text/plain');
        echo 'Hello World';
    ?>

この場合, http://<server>/hello.php といった URL でアクセスすると, "Hello World" というテキストが返って来ます. しかしながら, http://<server>/php/hello.php といった URL でアクセスすると, 403 Forbidden となります. ここまではいいでしょう.

次に, /opt/wwwindex.html があると仮定し, /opt/www/phpopen-external-file.php という名前で, 次のようなスクリプトを置いてみます.

    <?php
        header('Content-Type: text/plain');
        $file = fopen('../index.html', 'r');
        if ($file != FALSE) {
            while (!feof($file))
                echo fgetc($file);
            fclose($file);
        }
    ?>

ここで http://<server>/open-external-file.php にアクセスし, もし index.html の HTML ソースが返って来れば chroot が機能していないということになりますし, 指定されたファイルを開くことができない旨のエラーメッセージなどが表示されれば chroot は機能しているということになるでしょう.

実際に試してみたところ, 次のようなメッセージが返って来ましたので,

<br />
<b>Warning</b>: fopen(../index.html): failed to open stream: No such file or directory in <b>/open-external-file.php</b> on line <b>3</b><br />

たしかに, chroot は機能しているように思われます.

よくわからない問題

一応, わたし自身は, ここまででひとまず満足だったのですが, どうも気になる話題が目についてしまいました. untitled: Chroot PHP-FPM and Apache というブログ記事 (Oct 16, 2011) に書かれていたものです. Apache と PHP-FPM の組み合わせで chroot を利用すると, 一部のサーバー変数 (という呼び方でいいのか?) の値が別のトラブルの種になりかねない, ということを言っているようです. また, これに関連して, PHP :: Bug #62279 :: PHP-FPM chroot never-solved problems (extends #55322) というものの存在も知りました.

それらを読むと, どうも SCRIPT_FILENAMEPATH_TRANSLATEDDOCUMENT_ROOT が chroot されない状態になっている, とのことです. しかし, これ, わたしにはまだよくわかりません. それらが, たとえばどういう問題を引き起こしうるのでしょう.

ちなみに, 次のようなスクリプトを書いて確かめてみました.

    <?php
        header("Content-Type: text/plain");
        $variables = $_SERVER;
        ksort($variables);
        foreach ($variables as $key => $value) {
            echo $key;
            echo " = ";
            echo $value;
            echo "\n";
        }
    ?>

PHP では, 連想配列のコピーの仕方は, これでいいのでしょうか. これでいいっていう情報がいくつか見つかったので, これでいいに違いないと思うことにしていますが, いずれにせよ単にアルファベット順に並べ替えて少しでも見やすく表示したかっただけであります.

出力結果から, いくつか抜粋してみますと, 次のとおりでした.

    CONTEXT_DOCUMENT_ROOT = /opt/www
    CONTEXT_PREFIX = 
    DOCUMENT_ROOT = /opt/www
    FCGI_ROLE = RESPONDER
    GATEWAY_INTERFACE = CGI/1.1
    
    PHP_SELF = /server-variables.php
    QUERY_STRING = 
    
    REQUEST_URI = /server-variables.php
    SCRIPT_FILENAME = /server-variables.php
    SCRIPT_NAME = /server-variables.php

まず, 前述の記事に挙げられていたもののうち, SCRIPT_FILENAME は, どうなんでしょう, chroot されているように見えます. 次に, PATH_TRANSLATED は, 見つけられませんでした. そして, DOCUMENT_ROOT だけは, たしかにファイルシステム上の絶対パスが保持されていますので, chroot されていないと言えば, そう言えそうです.

しかし, どうなんでしょう. ドキュメントルートの絶対パスが外部に知られてしまう可能性があることを除けば, これを悪用したどんな攻撃がありうるのか, まだちょっと思いつきません. また, 前述の記事などでは, そういう観点よりも, むしろドキュメントルートに依存したスクリプト (既存のプラグインなど?) が正しく動作しなくなる可能性があるので, シンボリックリンクを張りまくらないといけないのが面倒だ, といったことに力点があるような感じがします. そうなのでしょうか.

まとめ

よくわかりませんが, 以上です. おしまい

No comments:

Post a Comment