http://www.openbsdsupport.org/ApacheSuexecChroot.html
此译文仅供JR社区会员测试探讨,请勿转载多谢!
================================================
介绍
原因
如果你和我一样每天忙忙碌碌,当你设置好一台多用户的web服务器后肯定不想老是惦记着这台服务器,所以必须在设置时就让它尽量地保证安全,所以你自然而然地想到要使用OpenBSD平台, 而你如果使用的是apache,你自然想将其限制在一个chroot jail里, 而且你还要使用suexec以便让所有的客户在各自的环境里进行定制。
不过先等等! 事实上默认的OpenBSD安装并既没有考虑启用suexec也没有考虑如何chroot它! 也许你是看到自己的error.log文件有类似的错误才找到这里来的:
代码: 全选
[error] Premature end of script headers
而且来此之前你可能已经尝试着做了一些工作,例如让suexec记录一下运行日志, 你可能得到类似这样的错误信息:
代码: 全选
command not in docroot
什么是OPENBSD?
为什么用它, 它可是世上最安全的操作系统! 先尽快在你的计算机上安装它, 然后了解一下OpenBSD Journal, 或者有关OpenBSD的一些奇闻轶事。
什么是SUEXEC?
suexec是Apache服务器的一个特性。它的意思是 "Switch User For Exec",也就是以不同的用户身份运行,简而言之,它允许Apache以不同的用户的身份为虚拟主机运行cgi程序, 这样一来,一台虚拟主机的 cgi 程序就不能毁坏其它的虚拟主机了。 这部分软件由Apache本身的几行程序代码和一个在需要时被Apache调用的可运行的二进制包(/usr/sbin/suexec)构成。
现在你明白了, 正常情况下所有的 CGI 脚本用 /var/www/conf/httpd.conf Apache配置文件里定义的 user/group 的身份来运行,在OpenBSD的默认情况下这个user是www,group也是www,
也就是
代码: 全选
user = www
group = www
如果启用了suexec,当一个用户的脚本运行时, 它只能以自己的user id来运行。SUEXEC的这种特性可以让管理员 (就是你!) 可正确地在多个cgi-bin目录里设置权限以便用户的 cgi 脚本可以在自己的 cgi-bin 目录下进行读写操作,也就是不能在其它用户的cgi-bin里读写了。
什么是CHROOT?
chroot是修改特定程序的 root 目录的一种操作。一个运行在chroot的环境下的程序不能访问chroot目录以外的文件。这种方法可以很方便地让不受信任的程序运行在一个特制的"sandbox"里。
Apache可能正是你不太信任的程序之一。它的输入内容可能来自世界上任何地方的web浏览器,包含那些想通过Apache攻克你主机的家伙。通过将Apache封进一个chroot的环境中, 即便你的计算机被攻陷,你仍旧可以最大限度地减少损失。
什么是虚拟主机?
通常情况下, 我们上面提到的系统管理员会将多个web站点放在同一台服务器上。要实现这样的目的,你需要让web服务器软件分析那些进站请求, 并将这些请求发送到文件系统的正确位置上。你可能有5个用户,他们分别有自己的web目录,并且提供不同的内容服务。你需要让你的服务器软件分析具体的进站http请求是针对哪个域名的, 并且将请求提交到正确的用户web目录。这个工作正是由Apache的Virtual Hosts来完成的。
要实现这样的功能, 你只需要编译一下 /var/www/conf/httpd.conf 文件,并在 <VirtualHost> 这一小段内对每个域名进行设置。
不过问题是
SUEXEC没有考虑CHROOT
简而言之, 在编译suexec的时候, 已经有了一系列设定好的配置选项, 编译完成后这些配置就无法改变了。如果你运行 /usr/sbin/suexec -V ,你会发现一个被称为 DOC_ROOT 的选项已经被编译在里面了。 这个 DOC_ROOT 指定了一个目录,所有的 cgi 程序必须都放在这个目录里以便suexec可以正常工作。
所以这里有一个奇怪的现象: 当你chroot了Apache后 (例如, 在 /var/www 里), 它与chroot环境之外的联系就被切断了。你已经知道了, 不是吗? 不过,请看一下 /var/www (或者不管你选择chroot在哪里),你能找到一个 var 目录吗? 估计没有。 所以就出现了suexec以为你的文件在 DOC_ROOT 目录下的某个地方 (默认情况下是 /var/www/htdocs 目录), 而事实是你的文件被认为chroot在 /htdocs 目录里。
照我原来的想法,要解决这个问题,首先通过在 /var/www 下创建一个带有www链接的 var 子目录,这个链接指回 /var/www,不过我发现这样也不行,suexec的error log现在显示:
代码: 全选
emerg: cannot get current working directory
这样做可行吗? 看起来不像。不过, 当你查阅一下suexec的源代码后, 你会发现它使用 getcwd() 来寻找当前的工作目录, 而这个函数传递这些链接时好像它们并不存在。 这时你是否有陷入泥潭的感觉?
解决方法
重新编译SUEXEC
对初学者来说你不得不重新编译suexec, 并且要将 DOC_ROOT 设置为 /cgi-bin (在chroot环境里这个目录已经存在了)。所以你只需到 /usr/ports 目录下,然后... 等等! suexec不在那里! 这是因为suexec是Apache的一部分, 所以你也要重新编译 Apache 。可没有这个版本的Apache的port, 因为它是OpenBSD的默认安装组件。 Apache在OpenBSD的源代码树里。别担心, 编译Apache很容易 (即便需要提供SSL支持!):
代码: 全选
# cd /usr
# export CVSROOT=:pserver:anoncvs@www.openbsd.org:/cvs
# cvs checkout src/usr.sbin/httpd && cvs logout
现在你可以看见正在下载这些疯狂的代码...
代码: 全选
# cd src/usr.sbin/httpd
# ./configure --with-layout="OpenBSD" --suexec-docroot="/cgi-bin" --suexec-logfile="/logs/suexec_log" --enable-suexec \
--enable-module=ssl --enable-module=so --enable-module=auth_anon --enable-shared=auth_anon \
--enable-module=expires --enable-shared=expires --enable-module=headers --enable-shared=headers \
--enable-module=auth_db --enable-shared=auth_db --enable-module=auth_dbm --enable-shared=auth_dbm \
--enable-module=auth_digest --enable-shared=auth_digest --enable-module=cern_meta --enable-shared=cern_meta \
--enable-module=define --enable-shared=define --enable-module=digest --enable-shared=digest \
--enable-module=info --enable-shared=info --enable-module=log_agent --enable-shared=log_agent \
--enable-module=log_referer --enable-shared=log_referer --enable-module=mime_magic --enable-shared=mime_magic \
--enable-module=mmap_static --enable-shared=mmap_static --enable-module=proxy --enable-shared=proxy \
--enable-module=rewrite --enable-shared=rewrite --enable-module=speling --enable-shared=speling \
--enable-module=unique_id --enable-shared=unique_id --enable-module=usertrack --enable-shared=usertrack \
--enable-module=vhost_alias --enable-shared=vhost_alias
如果没有出现错误, 你就可以编译了...
代码: 全选
# make
# make install
现在没有出现任何编译错误吧? 干得不错! 现在新的 "suexec" 已经编译好了, 你现在可以运行一下 "/usr/sbin/suexec -V" ,那个 DOC_ROOT 现在应该设置为 /cgi-bin 了。
配置SUEXEC并将其安装到CHROOT环境里
恭喜, 你已经重新编译了Apache。似乎看起来一切会越来越好。不过, 朋友,请等一下! 在你准备坐在走廊上品尝咖啡前你还需要解决一些小问题。
首先, suexec需要设置为所有者是root,而且需要设置为自己的sticky bit。当在程序上设置了一个sticky bit以后, 那么在其运行时它会获得所有者的权限 (而不是运行此程序的用户的权限)。所有人是root用户、而且设定了sticky bit,这两者结合在一起就允许 suexec 可成为任何用户 (为了用那个用户的身份运行 cgi 程序)。 所以我们要运行类似的命令:
代码: 全选
# chown root:wheel /usr/sbin/suexec
# chmod 4755 /usr/sbin/suexec
Christian Pedaschus这样警告过: 在OpenBSD里默认情况下, 如果你单独创建了一个 /var 分区, 它将会被挂载为 "nosuid" flag set. 这对suexec来说简直就是灾难了, 因为这不允许suexec改变自己的user id。 要修正这个问题, 编辑 /etc/fstab 文件,删除 /var 目录那行的 "nosuid" 。感谢Christian, 给我出了这么好的主意!
接下来, 还要做一些琐碎的工作。 看起来你还想为所有的用户提供他们自己的 cgi-bin 目录, 而上面所说的 "用户" 的group(gid 10)则应该是其那些目录的group所有者。这就是你使用suexec的目的。这里还有一个问题... suexec为了防止具有特权的用户或组运行程序 , 所以它有一个自己的"minimum uid" 和 "minimum gid" 的概念—— 而在OpenBSD的Apache源代码里,suexec的默认 "minimum uid" 和 "minimum gid" 设置为1000。这样如果用户的 uid 或者 gid 数值小于1000,suexec不会运行任何脚本。这防止了 "root" user 或者 "wheel" (admin) group 的成员运行脚本, 所以这对安全来说是一个很好的做法。不过... 如果任何 cgi 脚本的所有者为 "users" group (gid 10), 则suexec会拒绝运行这些脚本! 要解决此问题,正确的方法是让你的 "users" group有一个高数值的 gid (比如说,2020), 像这样:
代码: 全选
# groupmod -g 2020 users
不过麻烦的是, 你需要找到所有者为 group 10 (the old "users" group gid)的所有文件, 然后重新正确地设置每个文件的所有权。所以对于那些所有者为 group "10" 的文件, 这样:
代码: 全选
#chown :users <filename>
现在我们让suexec运行在chroot环境里。看起来一定要把suexec拷贝进chroot环境。这是一个有趣的现象。 suexec需要位于 /usr/sbin ,这样 Apache 能够找到他并启用它 (这发生在httpd真正chroot自己“前”) -- 然后呢suexec还需要位于 /var/www/usr/sbin 这样Apache可以在自己被chroot后找到它。所以我们还要找到suexec用哪些库文件, 并且将这些库文件和suexec二进制包拷贝到chroot环境里:
代码: 全选
# ldd /usr/sbin/suexec
/usr/sbin/suexec:
Start End Type Ref Name
00000000 00000000 exe 1 /usr/sbin/suexec
030fc000 23103000 rlib 1 /usr/lib/libm.so.2.0
0e97a000 2e985000 rlib 1 /usr/lib/libssl.so.10.0
0e605000 2e633000 rlib 1 /usr/lib/libcrypto.so.12.0
0f766000 2f797000 rlib 1 /usr/lib/libc.so.38.2
0366c000 0366c000 rtld 1 /usr/libexec/ld.so
好了,现在我们将所有这些拷贝到chroot环境里:
代码: 全选
# mkdir -p /var/www/usr/lib
# cp /usr/lib/libm.so.* /var/www/usr/lib
# cp /usr/lib/libssl.so.* /var/www/usr/lib
# cp /usr/lib/libcrypto.so.* /var/www/usr/lib
# cp /usr/lib/libc.so.* /var/www/usr/lib
# mkdir -p /var/www/usr/libexec
# cp /usr/libexec/ld.so /var/www/usr/libexec
# mkdir -p /var/www/usr/sbin
# cp /usr/sbin/suexec /var/www/usr/sbin
现在我们说一些真正的窍门。suexec需要一些文件来协助其工作。 第一个也是最重要的是, 在小的chroot jail环境里, 它需要分辨出group和gid以及user和uid之间的对应关系, 然后还有一些类似的工作。如果suexec无法在chroot jail里找到相关的信息,它会给出一条类似这样的出错信息:
代码: 全选
invalid target user name:
invalid target user id:
invalid target group name:
invalid target group id:
failed to setgid:
failed to setuid:
要知道需要哪些文件,你别无选择,只能用 ktrace (它跟踪系统调用)来运行Apache的httpd进程, 然后看一下哪些文件开启失败。不过,你还算幸运,因为我已经将这些脏活、累活替你干完了, 而且我也不介意将我的成果分享给你,将这些文件拷贝到所需的位置可以让suexec和Apache很好地运行:
代码: 全选
# mkdir -p /var/www/etc/
# cp /etc/group /var/www/etc/
# cp /etc/localtime /var/www/etc/
# cp /etc/login.conf /var/www/etc/
# cp /etc/passwd /var/www/etc/
# cp /etc/pwd.db /var/www/etc/
# mkdir -p /var/www/usr/share
# cp -R /usr/share/nls /var/www/usr/share
让PERL工作在CHROOT环境里
现今,很多人使用 perl 或 python 或者其它的程序来运行他们的 cgi 程序。如果你想让perl工作, 你需要将下列文件拷贝进chroot环境:
代码: 全选
# mkdir -p /var/www/usr/bin
# cp /usr/bin/perl /var/www/usr/bin/
# cp /usr/bin/perl5.* /var/www/usr/bin/
当然了,perl被链接到一些库文件上:
代码: 全选
# ldd /usr/bin/perl
/usr/bin/perl:
Start End Type Ref Name
00000000 00000000 exe 1 /usr/bin/perl
01a1a000 21a3b000 rlib 1 /usr/lib/libperl.so.10.0
0eded000 2edf4000 rlib 1 /usr/lib/libm.so.2.0
07b99000 27b9d000 rlib 1 /usr/lib/libutil.so.11.0
01999000 219ca000 rlib 1 /usr/lib/libc.so.38.2
0adac000 0adac000 rtld 1 /usr/libexec/ld.so
所以这些文件也要拷贝 (注意其中有些已经在chroot里了):
代码: 全选
# cp /usr/lib/libperl.so.* /var/www/usr/lib/
# cp /usr/lib/libutil.so.* /var/www/usr/lib/
现在我们创建一个用于测试的perl脚本。进入 /var/www/cgi-bin,然后创建一个名为 test.pl 的文件 (make sure it's executable by the "www" user or the "www" group!):
代码: 全选
#!/usr/bin/perl
print "Content-type: text/plain\n\n";
opendir(DIRHANDLE, "/");
@filenames = readdir(DIRHANDLE);
foreach $file (@filenames) { print "$file\n"; }
closedir(DIRHANDLE);
我们看一下他可否正常工作!
代码: 全选
# lynx http://localhost/cgi-bin/test.pl
.
..
conf
htdocs
icons
cgi-bin
logs
proxy
etc
usr
不错! chroot可以工作 - 我们让 perl 开启 / 目录,而它看到的正是 /var/www下的内容。现在你也许想拷贝一些 neat perl modules 到chroot环境中, 然后让这些 perl modules 可以正常的工作 (特别是这个 "net" modules):
代码: 全选
# cp /etc/resolv.conf /var/www/etc/
# cp /etc/services /var/www/etc/
# mkdir -p /var/www/usr/libdata /var/www/usr/local
# cp -R /usr/lib/Apache /var/www/usr/lib/
# cp -R /usr/libdata/perl5 /var/www/usr/libdata/
# mkdir -p /var/www/usr/local/libdata
# cp -R /usr/local/libdata/perl5 /var/www/usr/local/libdata/
# mkdir -p /var/www/usr/share
# cp -R /usr/share/zoneinfo /var/www/usr/share/
设置一台虚拟主机
我们假设要有一台VirtualHost运行在端口81上,而且它有自己的 cgi-bin 和 htdocs 目录, 所有的管理都由 "nobody" 用户进行。我们将创建 /var/www/htdocs/www.host.com ,其所有者为 user "nobody" 和 group "www" (这样,web服务器就可以在没有suexec的情况下读取它),然后我们再创建一个 /var/www/cgi-bin/www.host.com 目录,它的所有者是 user "nobody" 和 group "nobody" (这样,其他用户不能对其写操作)。而且我们也将把我们刚创建的 test.pl 文件拷贝到新的 cgi-bin 目录里, 这样我们就可以用它来测试了:
代码: 全选
# mkdir -p /var/www/htdocs/www.host.com
# echo "hello world" > /var/www/htdocs/www.host.com/index.html
# chown -R nobody:www /var/www/htdocs/www.host.com
# chmod 750 /var/www/htdocs/www.host.com
# mkdir -p /var/www/cgi-bin/www.host.com
# cp /var/www/cgi-bin/test.pl /var/www/cgi-bin/www.host.com/
# chown -R nobody:nobody /var/www/cgi-bin/www.host.com
# chmod -R 700 /var/www/cgi-bin/www.host.com/test.pl
在文件 /var/www/conf/httpd.conf 里添加一个 <VirtualHost> 小段,像这样:
代码: 全选
Listen 81
<VirtualHost *:81>
DocumentRoot /htdocs/www.host.com
ServerName www.host.com
User nobody
Group nobody
ErrorLog /var/www/logs/www.host.com-ERROR
CustomLog /var/www/logs/www.host.com-ACCESS combined
ScriptAlias /cgi-bin/ "/cgi-bin/www.host.com/"
</VirtualHost>
首先你需要主义的是,我们将 DocumentRoot 设置为 /htdocs/www.host.com —— 不过,当然在你的root目录里并没有这么个 "htdocs" 子目录。呵呵l, 事实上这里的 DocumentRoot 是指Apache将自己chroot在 /var/www 以后的root目录, 所以当它企图访问 /htdocs 时,它实际上访问的是 /var/www/htdocs —— 这也是我们希望看到的。就像你在上面的配置里可以看到的那样, cgi-bin目录也同理。
但是怎样处理那些 log 文件呢? 为什么它们能知道在 /var/www? 这个吗, 因为某些原因, Apache在chroot自己前已经开启了这些文件, 所以我们需要有 /var/www 在那里。真是这样, 不会吧? 我很肯定因为某些原因,实际情况确实是这样的, 如果你通篇阅读了Apache的源程序就会相信我了... 不过,源程序似乎有100万行,我不认为我们当中谁手里有足够的咖啡可以支撑到读完这些源代码。
好吧, 但是这里的 www.host.com 是指什么? 你应该将这里替换为自己的域名吗? 也许令你意外, 你不必太费心。我们临时将其添加到 /etc/hosts 文件里,并指向本地主机。你需要将这行加入到 /etc/hosts 文件里:
代码: 全选
127.0.0.1 www.host.com host
所以从现在开始,如果在你的机器上 ping www.host.com ,你将会看到的来自 127.0.0.1 回应。很简单, 是吧? 好了,现在我们重新启动 Apache (只需要运行 "Apachect restart"), 此时你的 VirtualHost 应该已经激活了:
代码: 全选
lynx http://www.host.com:81/cgi-bin/test.pl
.
..
conf
htdocs
icons
cgi-bin
logs
proxy
etc
usr
==================================================
原文链接:
http://www.openbsdsupport.org/ApacheSuexecChroot.html
中文翻译: leo@JR