suPHP + chroot パス変換パッチ
suEXECのPHP版、suPHPではサイト毎に実行ユーザーを変更することが出来ますが、その他の機能としてサイト毎にルートディレクトリを変更できるchroot機能も備えています。
例えば、hogeユーザーのホームディレクトリが/home/hoge/だとして、chrootしたとすると/home/hoge/を / とすることで、それ以外の階層のファイル(/etc/passwd等)にアクセスできないように保護することができます。
ところが、このchrootの機能が使い勝手が悪く、hoge.netというVirtualHostのDocumentRootが/var/www/hoge.net/htdocs/だとして、http://hoge.net/test.phpというURLにリクエストがあれば、以下のようになります。
例)suPHP + chrootの動作 / var/ www/ hoge.net/ ← suPHPでchrootする位置 htdocs/ ← ApacheのVirtualHostのDocumentRoot test.php ← /test.phpへのリクエストは、このスクリプトが実行される事を期待する。 image.png ← /image.pngへのリクエストは、そのまま期待通り。 var/ www/ hoge.net/ htdocs/ ← chrootしたことによって/var/www/hoge.net/htdocs/はこの位置になる。 test.php ← 実際にはここのスクリプトが実行される。ファイルがない場合は404 Not Foundとなる。
chrootしていない場合は /var/www/hoge.net/htdocs/test.phpというスクリプトファイルが実行されますが、/var/www/hoge.net/にchrootした場合、/var/www/hoge.net/htdocs/test.phpではなく、/var/www/hoge.net/をルートディレクトリとした/www/hoge.net/htdocs/test.phpとなる /var/www/hoge.net/var/www/hoge.net/htdocs/test.phpが実行されます。
この仕様が理解しにくい&冗長な為なのか、suPHPについて記載しているブログ等でもchrootについては触れずに記載しているサイトが多いようです。
chrootした場合でもDocumentRoot下のファイルが実行されるようにパスを変換するパッチを、だいぶ以前に作成して放置したままにしていましたが折角作成したので公開したいと思います。
このパッチを使用すると以下のようなイメージでsuPHP + chrootを利用できます。
例)suPHP + chroot パス変換パッチ / var/ www/ hoge.net/ ← suPHPでchrootする位置 htdocs/ ← ApacheのVirtualHostのDocumentRoot test.php ← /test.phpへのリクエストは、このスクリプトが実行される。 image.png ← /image.pngへのリクエストは、そのまま期待通り。
suphp + chroot パス変換パッチ
diff -Nuar suphp-0.7.1/doc/suphp.conf-example suphp-0.7.1-patch/doc/suphp.conf-example --- suphp-0.7.1/doc/suphp.conf-example 2008-04-01 04:14:47.000000000 +0900 +++ suphp-0.7.1-patch/doc/suphp.conf-example 2011-06-12 00:14:25.000000000 +0900 @@ -14,6 +14,9 @@ ;Path to chroot() to before executing script ;chroot=/mychroot +;Translate the script path when chroot() is used +;script_path_translation=false + ; Security options allow_file_group_writeable=false allow_file_others_writeable=false diff -Nuar suphp-0.7.1/src/Application.cpp suphp-0.7.1-patch/src/Application.cpp --- suphp-0.7.1/src/Application.cpp 2009-03-15 02:55:25.000000000 +0900 +++ suphp-0.7.1-patch/src/Application.cpp 2011-06-12 01:17:56.000000000 +0900 @@ -66,6 +66,7 @@ std::string scriptFilename; UserInfo targetUser; GroupInfo targetGroup; + std::string chrootPath; // If caller is super-user, print info message and exit if (api.getRealProcessUser().isSuperUser()) { @@ -104,7 +105,7 @@ // so do this before changing process permissions if (config.getChrootPath().length() > 0) { PathMatcher pathMatcher = PathMatcher(targetUser, targetGroup); - std::string chrootPath = pathMatcher.resolveVariables(config.getChrootPath()); + chrootPath = pathMatcher.resolveVariables(config.getChrootPath()); api.chroot(chrootPath); } @@ -116,6 +117,17 @@ // Prepare environment for new process newEnv = this->prepareEnvironment(env, config, targetMode); + if (chrootPath.length() > 0 && config.getScriptPathTranslation()) + { + std::string documentRoot; + scriptFilename.replace(1, chrootPath.length(), ""); + newEnv.setVar("SCRIPT_FILENAME", scriptFilename); + + documentRoot = env.getVar("DOCUMENT_ROOT"); + documentRoot.replace(1, chrootPath.length(), ""); + newEnv.setVar("DOCUMENT_ROOT", documentRoot); + } + // Set PATH_TRANSLATED to SCRIPT_FILENAME, otherwise // the PHP interpreter will not be able to find the script if (targetMode == TARGETMODE_PHP && newEnv.hasVar("PATH_TRANSLATED")) { diff -Nuar suphp-0.7.1/src/Configuration.cpp suphp-0.7.1-patch/src/Configuration.cpp --- suphp-0.7.1/src/Configuration.cpp 2008-03-29 22:02:36.000000000 +0900 +++ suphp-0.7.1-patch/src/Configuration.cpp 2011-06-12 00:11:20.000000000 +0900 @@ -112,6 +112,11 @@ #endif this->umask = 0077; this->chroot_path = ""; +#ifdef OPT_ENABLE_SCRIPT_PATH_TRANSLATION + this->script_path_translation = true; +#else + this->script_path_translation = false; +#endif } void suPHP::Configuration::readFromFile(File& file) @@ -157,6 +162,8 @@ this->umask = Util::octalStrToInt(value); else if (key == "chroot") this->chroot_path = value; + else if (key == "script_path_translation") + this->script_path_translation = this->strToBool(value); else throw ParsingException("Unknown option \"" + key + "\" in section [global]", @@ -250,3 +257,7 @@ std::string suPHP::Configuration::getChrootPath() const { return this->chroot_path; } + +bool suPHP::Configuration::getScriptPathTranslation() const { + return this->script_path_translation; +} diff -Nuar suphp-0.7.1/src/Configuration.hpp suphp-0.7.1-patch/src/Configuration.hpp --- suphp-0.7.1/src/Configuration.hpp 2008-03-29 22:02:36.000000000 +0900 +++ suphp-0.7.1-patch/src/Configuration.hpp 2011-06-11 23:59:14.000000000 +0900 @@ -58,6 +58,7 @@ int min_gid; int umask; std::string chroot_path; + bool script_path_translation; /** * Converts string to bool @@ -166,6 +167,11 @@ * Return chroot path */ std::string getChrootPath() const; + + /** + * Returns whether translate the script path when chroot is used + */ + bool getScriptPathTranslation() const; }; }; diff -Nuar suphp-0.7.1/src/config.h.in suphp-0.7.1-patch/src/config.h.in --- suphp-0.7.1/src/config.h.in 2009-03-15 03:07:57.000000000 +0900 +++ suphp-0.7.1-patch/src/config.h.in 2011-06-13 03:23:40.000000000 +0900 @@ -40,6 +40,10 @@ DOCUMENT_ROOT */ #undef OPT_DISABLE_CHECKPATH +/* Define if you want to enable translate the script path when + chroot is used */ +#undef OPT_ENABLE_SCRIPT_PATH_TRANSLATION + /* Defines path to logfile */ #undef OPT_LOGFILE
インストール
CentOS 5.xでの使用例を記載します。
まず、httpd-devel apr-develが必要なのでインストールされていない場合はインストールを行っておいて下さい。
yum install httpd-devel apr-devel
suPHPをパッチを当ててインストールします。
cd /var/tmp wget http://www.suphp.org/download/suphp-0.7.1.tar.gz wget http://d.hatena.ne.jp/pcmaster/files/suphp-0.7.1.patch.gz?d=y -O suphp-0.7.1.patch.gz tar xvzf suphp-0.7.1.tar.gz cd suphp-0.7.1 zcat ../suphp-0.7.1.patch.gz|patch -p1 ./configure \ --prefix=/usr \ --sysconfdir=/etc \ --with-apr=/usr/bin/apr-1-config \ --with-apxs=/usr/sbin/apxs \ --with-apache-user=apache \ --with-setid-mode=paranoid \ --with-php=/usr/bin/php-cgi \ --with-logfile=/var/log/httpd/suphp_log \ --enable-SUPHP_USE_USERGROUP=yes \ --with-min-uid=100 \ --with-min-gid=100 make sudo make install sudo sh -c "echo 'LoadModule suphp_module modules/mod_suphp.so' > /etc/httpd/conf.d/suphp.conf" sudo cp doc/suphp.conf-example /etc/suphp.conf sudo vim /etc/suphp.conf sudo /etc/init.d/httpd restart
suPHP + chrootの準備
php-cgiを実行するのに必要なライブラリをコピーします。
まず下記の内容を、mkphpjail.shとして保存します。
#!/bin/bash CHROOTDIR=$1 if [ -z "${CHROOTDIR}" ] then echo "Usage: ${0##*/} CHROOTDIR" exit 1 elif [ ! -d "${CHROOTDIR}" ] then echo "${CHROOTDIR}: Directory not found. " echo "please mkdir ${CHROOTDIR};" exit 1 fi mkdir -p ${CHROOTDIR}/{etc/php.d,htdocs,bin,usr/{bin,local/bin,lib64/php,share/{pear,zoneinfo}}} #cp -f /bin/bash ${CHROOTDIR}/bin/ cp -fp /etc/php.ini ${CHROOTDIR}/etc/ cp -fp /etc/php.d/* ${CHROOTDIR}/etc/php.d/ cp -fp /usr/bin/php-cgi ${CHROOTDIR}/usr/bin/ cp -Rfp /usr/share/zoneinfo/* ${CHROOTDIR}/usr/share/zoneinfo/ cp -Rfp /usr/share/pear/* ${CHROOTDIR}/usr/share/pear/ cp -Rfp /usr/lib64/php/* ${CHROOTDIR}/usr/lib64/php/ ldd ${CHROOTDIR}/{bin,usr/bin,usr/local/bin,usr/lib64/php/modules}/*|grep '/.*(0x'|sed -e 's,\s*\([^/]*=> \)\?/\(.*\) (0x.*,\2,'|sort|uniq|while read lib; do LIBDIR="${CHROOTDIR}/`dirname ${lib}`"; test ! -d ${LIBDIR} && mkdir -p ${LIBDIR}; cp -fpv /${lib} ${CHROOTDIR}/${lib}; done
mkphpjail.shを実行します。
$ chmod +x mkphpjail.sh $ mkdir -p /var/www/hoge.net $ ./mkphpjail.sh /var/www/hoge.net
php-cgiをjail環境で実行するのに必要なモジュールが、/var/www/hoge.net以下にコピーされます。
私の環境の場合、サイズは43MB程でした。
複数サイトを生成する場合でオーバーヘッドが気になる場合は、ハードリンク(rsyncで--link-destでコピーする等)にしてしまうのが良いかもしれません。
次にユーザーを追加します。
$ sudo useradd hogenet_user -d /var/www/hoge.net
次に、/etc/suphp.confを編集します。
chrootを設定しscript_path_translation=trueとすることでパス変換の機能が有効になります。
[global] ;Path to logfile logfile=/var/log/suphp.log ;Loglevel loglevel=info ;User Apache is running as webserver_user=apache ;Path all scripts have to be in docroot=${HOME}/htdocs ;Path to chroot() to before executing script chroot=${HOME} ;Translate the script path when chroot() is used script_path_translation=true ; Security options allow_file_group_writeable=false allow_file_others_writeable=false allow_directory_group_writeable=false allow_directory_others_writeable=false ;Check wheter script is within DOCUMENT_ROOT check_vhost_docroot=true ;Send minor error messages to browser errors_to_browser=false ;PATH environment variable env_path=/bin:/usr/bin ;Umask to set, specify in octal notation umask=0077 ; Minimum UID min_uid=100 ; Minimum GID min_gid=100 [handlers] ;Handler for php-scripts application/x-httpd-suphp="php:/usr/bin/php-cgi" ;Handler for CGI-scripts x-suphp-cgi="execute:!self"
Apacheの設定を行います。suPHP_UserGroupで実行するユーザーおよびグループを設定します。
<VirtualHost *:80> DocumentRoot /var/www/hoge.net/htdocs/ ServerName hoge.net ErrorLog logs/hogenet-error_log AccessLog logs/hogenet-access_log common suPHP_Engine on AddHandler application/x-httpd-suphp .php suPHP_AddHandler application/x-httpd-suphp suPHP_UserGroup hogenet_user hogenet_user suPHP_ConfigPath /etc/ <Directory "/var/www/hoge.net/htdocs/"> Options -Indexes Includes MultiViews FollowSymLinks AllowOverride All Order allow,deny Allow from all </Directory> </VirtualHost>
変更を反映させます。
$ sudo /etc/init.d/httpd restart || **確認 テストスクリプトを用意します。 >|bash| $ echo '<?php phpinfo(); ?>' > /var/www/hoge.net/htdocs/test.php $ sudo chown hogenet_user:hogenet_user /var/www/hoge.net/htdocs/test.php
ブラウザでアクセスし、phpinfoが表示され、DOCUMENT_ROOTが/htdocs/と変わっていれば正常に動作しています。
suEXECの動作により上記でオーナーを変更しないと実行できないはずですので、そちらも確認してみた方が良いでしょう。
以上、何かのお役に立てる事があれば幸いです。