元のアドレス: zodiacg.net/2015/08/php5-fpm-chroot/
chroot は php-fpm で設定でき、非常に優れた分離効果があり、システムのセキュリティを強化します。ただし、合理的で使いやすい php-fpm chroot 環境を構築するのは若干難しく、debootstrap などのツールを使用して完全な chroot 環境を構築するよりも面倒です。オンラインにはチュートリアルがいくつかありますが、ほとんどが乱雑であったり、古く、手順の説明がありません。ここでは、php-fpm の chroot 確立を確認するために多くの情報を参照します。
この記事では、Ubuntu 14.04.2 を例として取り上げます。php-fpm は、ppa:ondrej/php5-5.6 によって提供される PHP5.6 バージョンを使用します。これは、php-fpm およびシステム ディレクトリ構造と同じです。はシステムに付属しており、Debian システムと一貫性がある必要があります。 CentOSの調整はご自身で行ってください。
php-fpm の chroot 環境設定は、使用されるサーバー フロントエンドとは関係がなく、Apache/Nginx に chroot の実行を強制しません。もちろん、その方が安全かつ複雑です。
1. ディレクトリ構造を確立しますchroot ディレクトリは /var/www/chroot として選択され、ページ ファイルは /var/www/chroot/public に配置されます。
次のコマンドを実行して、基本的なディレクトリ構造を確立します:
bashmkdir -p /var/www/chroot/cd /var/www/chrootmkdir -p public bin dev tmp usr/sbin/ usr/share/zoneinfo/ var/run/nscd/ var/lib/php5/sessions var/wwwcp -a /dev/zero /dev/urandom /dev/null dev/ #注3chmod --reference=/tmp tmp/chmod --reference=/var/lib/php5/sessions var/lib/php5/sessions #注4chown -R root:root . #注2chown -R www-data:www-data public/ #注2cd var/wwwln -s ../.. chroot #注1
現時点でのディレクトリ構造は次のとおりです。後でいくつかの新しいものが追加されます:
/var/www/chroot/├── bin├── dev│ ├── null│ ├── urandom│ └── zero├── public├── tmp├── usr│ ├── sbin│ └── share│ └── zoneinfo└── var ├── lib │ └── php5 │ └── sessions ├── run │ └── nscd └── www └── chroot -> ../.. #注1
注 1: このソフト リンクは、Apache/nginx によって php-fpm に渡された SCRIPT_FILENAME が chroot に入った後にファイルが見つからない (php ページにアクセスすると「ファイル」が返される) という問題を解決するために使用されます。見つかりません")。
nginx を例にとると、SCRIPT_FILENAME は通常 $document_root$fastcgi_script_name に設定され、php-fpm に渡されるスクリプト パスは /var/www/chroot/public/index.php です。 php-fpm は chroot 環境にあるため、実際にアクセスしようとするパスは /var/www/chroot+/var/www/chroot/public/index.php になりますが、もちろんこれは存在しません。
そこで、ソフト接続を使用して chroot 環境の /var/www/chroot をルート ディレクトリにリンクすると、スクリプトに正常にアクセスできるようになります。
もちろん、SCRIPT_FILENAME を /public$fastcgi_script_name に設定することもできます。ただし、このようなハードコーディングは構成の移行には役に立たず、chroot 環境でのみ使用できます。非 chroot 環境に戻す場合は、構成を変更する必要があります。したがって、これを行うことはお勧めできません。 (ちなみに、$document_root を使用せず、ルート ディレクトリを直接ハードコードする古いチュートリアルが多数ありますが、これはもちろんお勧めできません)
注 2: chroot 環境は、 100%安全です。 chroot 環境での php-fpm の実行権限は www-data であるため、不要なアクセス権限を減らすために、必須でないディレクトリの所有者を root に設定することをお勧めします。 chroot はセキュリティと同等ではありません。chroot のベスト プラクティスにリストされているいくつかの原則を参照してください。より安全な観点から、bin、lib、sbin およびその他のディレクトリの読み取りおよび書き込み権限を削除し、実行可能権限だけを残すことが最善ですが、大きな違いはありません...
注 3 : cp -a は、ファイルの内容をコピーするだけでなく、ファイルのアクセス許可、モード、その他の情報もコピーします。これを使用すると、0、urandom、null の 3 つの主要なデバイス ファイルを直接コピーできます。 mknod の方が信頼性の高い方法のようですが、cp -a を使用しても問題ないようです。
注 4: chmod --reference=XXX は、後続の権限を設定するために XXX の権限を参照します。 tmp については触れません。重要なのは、次の var/lib/php5/sessions が PHP がセッション ファイルを保存するディレクトリであり、www-data には読み取りおよび書き込み権限が必要であるということです。設定後に見てみることをお勧めします。もちろんこの後テストもあります。
2.PHP-FPM の設定chroot 環境を構築するために新しい php-fpm 実行プールを作成します。 php-fpm.conf を直接変更することはお勧めできません。これは、複数の PHP サイトが存在する場合、chroot 環境を共有することになるためです。
実際、多くの php-fpm チュートリアルは php-fpm プールの設定を無視しており、多くの人がサーバー上のすべてのサイトの設定セット、特に php.ini 設定を共有することになります。実際には無理がある。プールはサイトのニーズに応じて個別に作成し、その中でパラメーターを調整する必要があります。
/etc/php5/fpm/pool.d/ の下に新しい chroot.conf を作成します (php-fpm.conf によって呼び出されるように、.conf で終わる必要があることに注意してください):
[chroot]user = www-datagroup = www-datalisten = /var/run/php-chroot.socklisten.owner = www-datalisten.group = www-datapm = dynamicpm.max_children = 5pm.start_servers = 1pm.min_spare_servers = 1pm.max_spare_servers = 3chroot = /var/www/chrootchdir = /public;security.limit_extensions = .phpphp_flag[display_errors] = onphp_value[date.timezone] = Asia/Hong_Kong;php_admin_value[session.gc_probability] = 1;php_admin_value[open_basedir] = "/tmp/:/public/:/var/www/chroot/public/"
前のパラメータは比較的馴染みのあるものです。 chroot を有効にするには、構成された環境のルート ディレクトリに chroot を設定するだけです。 php5-fpm -t を実行してテストした後、service php5-fpm reload を使用して新しいプールを有効にします。もちろん、バックエンドは Apache/nginx の対応する構成でセットアップする必要があります。
最後の数行について言及します。最後から 4 行目では、display_errors をオンにして、後で chroot で PHP の機能をテストできるようにします。テスト後に必ずコメントアウトしてください。
session.gc_probability を設定すると、php プロセスが独自にセッションを削除してリサイクルできるようになります。通常、セッションはPHPが追加したcronタスクによってクリーンアップされますが、chroot環境ではPHPが自動的にセッションをクリーンアップしてくれないようです。もちろん、自動的に実行されるスクリプトを cron.d に追加して自分でクリーンアップすることもできるため、このオプションをオンにする必要はありません。
3. Chroot 環境で PHP のさまざまな機能を修復します/var/www/chroot/public に新しい test.php を作成し、次の内容を記述します:
php<?phpsession_start();header( "Content-Type: text/plain" );echo( gethostbyname( "localhost" )."\n" );print_r( getdate() );mail( "your@address", "subject", "message" );
メイン テスト関数は次のとおりです: セッション、DNS 解決、時刻と日付、メール mail() 関数。
访问上面的测试页面,提示No such directory or file或者Permission denied说明session配置不正确。gethostbyname返回的不是127.0.0.1或者::1,则说明DNS解析没有生效。提示timezone database is corrupt之类的,说明时间和日期有错误。mail()也会有各种错误提示。
session就不提了,设置好目录权限就没有问题。主要处理一下后面三个问题,也是php的chroot环境主要需要处理的内容。
mail()的解决方法大同小异,放后面谈。前面的域名解析等问题这里我介绍两种解决方法。方法1是参考Kienzl的简便方法,方法2是大部分教程采用的方法。
nscd是(e)glibc的“Name Service Caching Daemon”。除了处理gethostbyname()这样的函数外,也处理getpwnam()等需要访问/etc/passwd的函数。(e)glibc访问nscd的unix socket,/var/run/nscd/socket来通过nscd获取这些内容,如果不能连接到nscd则转而自行进行解析。
也就是说,只要装好nscd,并且让chroot环境里的程序能够访问到socket连接上nscd,就可以把chroot环境内的解析请求转由chroot外顺利进行了。由于/var/run一般是tmpfs,硬链接无法跨文件系统使用,所以可以使用mount -bind来把/var/run/nscd目录mount到chroot环境中同样的位置去即可。
同样的道理,用mount -bind把/usr/share/zoneinfo目录mount到chroot环境里,配合在php-fpm的pool里设置date.timezone就可以非常直接而暴力的解决时区问题。
先执行apt-get install nscd安装nscd,然后为了能够让mount -bind自动执行,把下面的脚本存为/etc/init.d/php-chroot
bash#!/bin/sh### BEGIN INIT INFO# Provides: php5-fpm-chroot-setup# Required-Start: nscd# Required-Stop:# Default-Start: 2 3 4 5# Default-Stop: 0 1 6# Short-Description: Bind-mounts needed sockets and data into a php-fpm-chroot### END INIT INFOCHROOT=/var/www/chrootDIRS="/var/run/nscd /usr/share/zoneinfo"case "$1" in start) $0 stop 2>/dev/null for d in $DIRS; do mkdir -p "${CHROOT}${d}" mount --bind -o ro "${d}" "${CHROOT}${d}" done ;; stop) for d in $DIRS; do umount "${CHROOT}${d}" done ;; *) echo "Usage: $N {start|stop}" >&2 exit 1 ;;esacexit 0
执行update-rc.d php-chroot defaults来让脚本在启动时执行。如果有多个chroot环境以及多个目录需要bind-mount,可以自行添加一个循环改写。
这个方法的好处是简单易行,不需要拷贝大量etc下的配置文件和库文件到chroot环境中。使用nscd在解决域名访问的问题过程中也顺道解决了/etc/passwd和/etc/group。但是bind-mount和nscd的安全性尚没有确切的说法,只能说so far so good。另外mount -bind会消耗一定的系统资源,有评论称大约一个mount 大概会消耗500k内存,所以对于大量的chroot环境(几百个)不见得适合。
这是最传统而常用的方法,也相对比较复杂。到底拷贝哪些配置、哪些库文件因发行版和软件版本而异,很难有定论也不好调试。而且一旦系统升级,对应的库文件也需要进行更新,工作量很大。我没有采用这个方法,但是简要的介绍一些比较靠谱的方法分享一下。
域名解析。需要拷贝/etc/resolv.conf,/etc/hosts,/etc/nsswitch.conf到chroot环境下的etc目录下。还需要拷贝一系列的库文件,主要是libnss_*.so,libresolv.so,libsoftokn3.so。具体libnss_*.so拷贝哪些,可以打开nsswitch.conf看列出了哪些。
时区配置。拷贝/etc/localtime,/usr/share/zoneinfo/zone.tab,和/usr/share/zoneinfo目录下所使用时区的文件。
其它常用配置。/etc/passwd和/etc/group有时也是需要的,但是内容似乎可以伪造,至少可以选择性的填写不用完全拷贝主系统里的。
如果使用的时候仍然出现问题,可以使用strace来查看php进行了哪些调用使用了哪些库文件。先执行:
bashps aux | grep php | grep 'chroot' #chroot是php的pool名
查看pool的进程pid(可以在pool设置里先把子进程数目限制到1个方便调试)。然后执行:
bashstrace -p 进程pid -o chroot1.txt& #有多个子进程就修改pid执行多次,输出改为chroot2/3.txt存到不同文件里
此时在页面里执行各种函数,然后查看输出文件里记录了哪些库文件,对应拷贝到chroot环境里即可。
这个方法很麻烦,尤其是第一次安装设置和后续系统更新时。当然身为运维人员写写shell脚本简化工作肯定是基本功了。这种方法没有额外的内存消耗,可以部署大量chroot环境,当然硬盘消耗会高一点,而且安全性也经历了长久的考验
如果是使用WordPress,也可以利用MailChimp等插件不使用系统自身的邮件服务。事实上因为垃圾邮件的标准日益严格,和VPS主机商的限制,我现在更倾向于干脆不在系统里部署邮件服务了,所以php的mail()函数算是被废掉了……当然如果需要的话也可以很简单的设置好的。
php的mail()函数是使用system()调用sendmail进行邮件发送操作,所以需要chroot环境里有能够调用的sendmail程序即可。常见的替代品是mini_sendmail,这里多介绍ssmtp,msmtp也类似。
system()调用产生的命令行是/bin/sh -c command。在chroot环境中调用外部程序必须存在/bin/sh,一个基本的shell。通常选择拷贝dash:
bash#cp /bin/dash /var/www/chroot/bin/sh
注意运行ldd /bin/dash观察需要拷贝哪些库文件。我这里的回显是:
bashldd /bin/dash linux-vdso.so.1 => (0x00007fff779fe000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f165620f000) /lib64/ld-linux-x86-64.so.2 (0x00007f16567fc000)
第一条那个只列了个文件名,=>后面也没有文件的基本上都是不用管的。剩下的库文件基本的原则是如果列出的是/lib64,就拷贝到chroot环境下的/lib64,如果列出的是/lib,虽然有很多发行版,大部分库文件包括libc.so是在/lib/x86_64-linux-gnu/目录下的,也直接拷贝到chroot环境的/lib目录下即可,是可以正常找到的。
但是!
前面那句“必须存在/bin/sh,一个基本的shell”其实并不是真的,对于mail()只要有一个能接受-c参数调用后面的命令的程序就可以了。所以Kienzl写了这样一个程序:
c#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h> #define MAXARG 64 int main( int argc, char* const argv[] ) { char* args[ MAXARG ] = {}; if( argc < 3 || strcmp( argv[1], "-c" ) != 0 ) { fprintf( stderr, "Usage: %s -c <cmd>\n", argv[0] ); return 1; } { char* token; int i = 0; char* argStr = strdup( argv[2] ); while( ( token = strsep( &argStr, " " ) ) != NULL ) { if( token && strlen( token ) ) args[ i++ ] = token; if( i >= MAXARG ) return 2; } } return execvp( args[0], args );}
保存成sh.c执行:gcc sh.c -o sh -static然后把sh拷贝到chroot环境的/bin目录下即可。
这样一个不完全的shell从一定程度上也算是增强了chroot环境的安全性了。
mini_sendmail似乎专为chroot环境而生。调用mini_sendmail后,它会转而访问本机的25端口,通过本机的邮件服务来发送邮件。所以如果主环境有安装postfix/exim4等邮件服务的话可以使用mini_sendmail来在chroot环境中发送邮件,这是最简单的方法。
mini_sendmail的安装很简单:
bashwget http://www.acme.com/software/mini_sendmail/mini_sendmail-1.3.8.tar.gztar zxf mini_sendmail-1.3.8.tar.gzcd mini_sendmail-1.3.8makecp mini_sendmail /var/www/chroot/usr/sbin/sendmail
最后一行自行修改chroot环境的目录。切记要拷贝到chroot环境的/usr/sbin目录下并且命名为sendmail。否则的话要在pool里自行设置ini参数的sendmail_path来指导php找到sendmail程序。
由于mini_sendmail默认就是静态链接,所以也无需拷贝其它的库文件了。
对于本机没有安装邮件服务的情况,就不能使用mini_sendmail了。ssmtp和msmtp都支持把接收到的邮件发送请求转而通过其它SMTP服务器来发送。需要注意的是由于ssl支持需要更多更复杂的库文件和配置,所以不建议为两者编译ssl支持……下面以ssmtp为例介绍一下。
bashwget ftp://ftp.debian.org/debian/pool/main/s/ssmtp/ssmtp_2.64.orig.tar.bz2tar jxf ssmtp_2.64.orig.tar.bz2cd ssmtp_2.64./configure --prefix=/ #别忘了prefixmake #千万别手抖make installcp ssmtp /var/www/chroot/usr/sbinmkdir -p /var/www/chroot/etc/ssmtpcp ssmtp.conf revaliases /var/www/chroot/etc/ssmtp #配置文件cd /var/www/chroot/usr/sbinln -s ssmtp sendmail
同样记得ldd然后把对应的库文件拷贝过去。另外ssmtp需要/etc/passwd和/etc/group,如果上面没有使用nscd需要拷贝/伪造这两个文件。
ssmtp需要配置。ssmtp.conf文件配置如下:
bashroot=admin@example.com #其实这行好像可以乱写mailhub=smtp.example.com #smtp服务器地址hostname=myexample.com #此处的hostname似乎会用于产生默认的“root@myexample.com”形式的发件人地址AuthUser=admin@example.com #此处使用真实的登录用户名AuthPass=password #密码FromLineOverride=YES #允许改写发件人
revaliases里配置每个用户在使用ssmtp时使用的“发件人”地址和smtp服务器地址。可以不配置,但是文件要有。具体格式是:
bash# 本地用户名:发件人地址:smtp服务器[:端口(默认25)]root:admin@example.com:smtp.example.comwww-data:noreply@example.com:smtp.example.com
可以使用chroot(指真正的chroot命令)做个测试:
bashchroot /var/www/chroot /bin/sh #此时/bin/sh一定要是真正的shellecho "Subject: test"|sendmail -v username@server.com #替换邮件地址为自己的
此时php的mail()函数应该就可用了。
4.其它问题/var/www/chroot/├── bin│ └── sh├── dev│ ├── null│ ├── urandom│ └── zero├── etc│ └── ssmtp│ ├── revaliases│ └── ssmtp.conf├── lib│ └── libc.so.6├── lib64│ └── ld-linux-x86-64.so.2├── public├── tmp├── usr│ ├── sbin│ │ ├── sendmail -> ssmtp| │ └── ssmtp│ └── share│ └── zoneinfo│ ├── 大量时区的目录结构│ └── zone.tab└── var ├── lib │ └── php5 │ └── sessions ├── run │ └── nscd │ ├── nscd.pid │ └── socket └── www └── chroot -> ../..