说实话,FreeBSD的Jail机制已经存在很长时间了,从4.0版本开始就融入了系统内核。它的核心设计其实非常直观:将进程及其所有子进程“隔离”起来,严格限制它们的视野和活动范围。尤其值得关注的是,Jail能够与FreeBSD自带的secure_level协同工作,进一步约束Jail内root用户的权限——即便你在Jail内部拥有root身份,也无法对宿主机系统进行任何操作。
可以这样理解:假设某个应用程序在普通系统中运行,某天突然被曝出严重的远程代码执行漏洞。正常情况下,攻击者很可能利用这个漏洞直接获取系统的root权限,进而完全控制整个服务器。但如果这个应用被放置在Jail内部运行,情况就完全不一样了——即便攻击者成功攻破了Jail里的应用,也无法获得root权限,更无法访问Jail外部的任何资源。你可能会问,这样的隔离到底有什么用?简单来说,即使Jail内部的应用被彻底攻破,它对宿主机系统的威胁也被降到了最低点。这正是Jail在系统安全管理和风险防护上的核心价值:有效防范未知漏洞的扩散,阻断潜在威胁的纵向移动。
通常来说,Jail主要分为两大应用方向:
第一类:限制应用程序的活动能力
比如FTP服务器、DNS服务器这类服务——像wu‑ftpd、bind这样屡屡曝出安全漏洞的“知名”软件,特别适合部署到Jail环境中。这样一来,即便某天又被发现新漏洞,也不至于让整个机器直接沦陷。
实现这类应用并不复杂。如果你在Linux下使用过chroot,那么上手会非常顺畅。下面我们用一个具体案例来说明完整流程:假设要把pure‑ftpd放到Jail里运行。
首先按常规方式安装好应用程序。pure‑ftpd的默认安装位置在/usr/local/sbin和/usr/local/bin,/etc目录下还有一些相关配置文件。整体结构虽然不算特别整洁,但它运行所需的核心文件并不多,主要包括/usr/local/sbin/pure‑ftpd和对应的配置文件。
接下来,使用ldd命令查看pure‑ftpd需要哪些运行时库:
tester# cd /usr/local/sbin
tester# ldd pu re-ftpd
pu re-ftpd:
libcrypt.so.2 => /usr/lib/libcrypt.so.2 (0x2807b000)
libpam.so.1 => /usr/lib/libpam.so.1 (0x28094000)
libc.so.4 => /usr/lib/libc.so.4 (0x2809d000)
这样一来,任务清单里就多了/usr/lib/......这些依赖文件。用同样的方法,还可以找出其他需要放入Jail的程序所依赖的动态库。收集完信息后,开始构建Jail目录树。这里假设Jail环境建在/jail下:
tester# mkdir -p /jail/usr/{lib,libexec,local/sbin,local/bin,local/etc,etc,var/run,var/log}
然后把上面列出的库文件(例如libcrypt.so.2等)复制到对应的目录。千万别忘了还有一个至关重要的文件——ld-elf.so.1,虽然ldd没有提示,但缺少它应用程序根本无法启动。
Jail命令的基本格式是:
jail path hostname ip-number command
现在开始在其中运行pure‑ftpd:
tester# jail /jail jailed.host.name $JAILED_IP_ADDR /usr/local/sbin/pu re-ftpd [options]
这些参数的含义是:/jail是Jail环境所在的位置,也是被隔离后应用程序“以为”自己所在的/根目录;jailed.host.name指定该Jail环境的主机名;$JAILED_IP_ADDR是打算提供FTP服务的IP地址;/usr/local/sbin/pu re-ftpd [options]是应用程序在Jail内的完整路径和启动参数。
用ps查看一下进程状态:
tester# ps -axf |grep pu reftpd
95 ?? IsJ 0:00.92 pu re-ftpd (SERVER) (pu re-ftpd)
注意那个J标志,它表示这些进程正在Jail下运行。如果某些管理工具无法正常工作,通常是因为找不到它们需要的文件。找到这些文件的位置后,创建一个软链接就能解决问题。
至此,一个针对应用程序的Jail就搭建完成了。
第二类:构造受控制的主机
相比第一类,第二类应用要复杂得多,但也正是Jail最吸引人的特性之一。在某些场景下,需要对外提供带有shell的管理性访问。例如,公司A的合作单位B需要在某台机器上获取shell甚至root权限来进行项目部署。这时就需要提供一个受控制的主机——用户可以在Jail内管理几乎所有需要的资源,唯独不能访问Jail不允许的部分。
要构建一个完整的Jail系统,首先需要一个当前版本操作系统的完整镜像。下面的脚本摘自FreeBSD 4.6R的man手册(4.5及更早版本在构建Jail目录树时存在问题,4.6才修正):
tester# cat >>/root/mkjail.sh
jailhome=/data/jail
cd /usr/src
mkdir -p $jailhome
make world DESTDIR=$jailhome
cd etc
make distribution DESTDIR=$jailhome -DNO_MAKEDEV_RUN
cd $jailhome/dev
sh MAKEDEV jail
cd $jailhome
ln -sf dev/null kernel
^D
tester# sh /root/mkjail.sh
执行完这个脚本后,/data/jail下就会生成一个完整的、根据当前源代码树编译出来的Jail目录树。
接下来:
tester# mkdir $jailhome/stand
tester# cp /stand/sysinstall $jailhome/stand
tester# jail $jailhome jailed.system.box 192.168.0.123 /bin/csh
(这时候获得了一个Jail下的shell)
jailed# /stand/sysinstall
通过sysinstall可以配置Jail系统常用的变量,比如时区、DNS、邮件服务等,以及Jail系统“启动”时需要运行的程序。如果你对系统足够熟悉,也可以手动一个一个完成。
一些关键的配置步骤:
- 复制
/etc/localtime到$jailhome/etc,确保Jail环境下的应用程序能获取正确的时间; - 复制
/etc/resolv.conf到$jailhome/etc/resolv.conf,让Jail内部能正确解析域名; - 在Jail内执行
newaliases,避免sendmail不断发出警告; - 如果打算运行inetd,需要修改其启动参数,加上
-a $LISTEN_ADDR选项(因为Jail无法自行获取当前系统的IP地址),在rc.conf中应写成:inetd_flags="-wW -a 192.168.0.123"; - 将宿主机自身的syslogd加上
-ss选项,避免它启动****端口;修改/etc/rc.conf添加syslogd_flags="-ss"(对$jailhome/etc/rc.conf同样处理); - 在Jail内创建一个空的
/etc/fstab,并在rc.conf中去掉网卡地址的绑定,这样Jail系统启动时就不会报错。
要让这个Jail系统真正运行起来,还需要给它分配一个可连接的IP地址。这个地址可以和实际环境处于同一子网,也可以在不同子网:
tester# ifconfig fxp0 192.168.0.123 netmask 0xffffffff alias
所有配置完成后,有两种方式可以将Jail系统启动:
一种是从Jail外部直接运行:
tester# jail $jailhome jailed.system.box $jail_IP_ADDR /bin/sh $jailhome/etc/rc
另一种是仅在Jail内启动ssh/telnetd这类远程访问服务:
tester# jail $jailhome jailed.system.box $jail_IP_ADDR /bin/sh $jailhome/bin/inetd -wW -a $jail_IP_ADDR
如果用于生产环境,推荐使用第一种方法,并将启动Jail的命令放在宿主机的/etc/rc.local脚本中。这样Jail系统会拥有比较完整的、类似于实际机器的工作环境。
整个工作流完成后,再配合定期的严格备份、安全检查和审计,就能构建出一个相当安全可靠的系统。一般的脚本小子已经无法对你的系统构成实质性威胁。即便是那些与黑帽社区接近的人,在漏洞公开之前拿到了实际攻击脚本,闯入你的系统,也只能在Jail内部活动。而且你还能清楚地掌握他何时进出系统、做了什么操作,这样一来,恢复系统和防范下一次攻击就会从容许多。
Jail系统管理中的几个注意事项
1. 用户ID管理: Jail内部的账户和密码与宿主机是相互独立的。但在Jail外部通过ps或查看Jail目录树内的文件时,Jail内部的UID会作为外部UID显示。因此,最好把Jail中的/etc/adduser.conf修改一下,将UID起始编号增大,例如设置为uid_start="5000"。这样在外部管理文件或进程时,就不会混淆文件或进程的归属了。
2. 能力限制: Jail内的任何活动都受到严格约束。像top、vmstat这类工具无法使用,mknod、dd等需要直接访问硬件的操作也无法执行。所以在Jail内部监控系统运行状态会比较困难。
3. 关闭Jail的方式: 远程关闭Jail有两种方法:一种是进入Jail后执行kill -TERM -1或kill -KILL -1,向所有Jail内的进程发送信号;也可以在Jail内运行/etc/rc.shutdown来关闭。本地操作则更加简单,直接杀掉所有带有J标记的进程即可。
4. 多Jail系统: 一个宿主机可以运行多个Jail,各个Jail之间互不干扰。有一个容易踩坑的地方:如果在Jail外部用jail $jailhome ... $application的方式运行了某个应用,之后又想通过jail $jailhome ... /bin/csh来管理它,实际上这是两个不同的Jail,彼此无法干涉。因此,推荐在Jail内除了应用软件之外,同时启动telnetd或sshd。这样通过远程登录获得的shell就和那些应用处于同一个Jail中,管理起来会方便很多。
5. 版本一致性: Jail系统内的所有应用软件版本号,应与外部宿主机保持一致。当外部系统源码同步并重新make world之后,最好也重新生成一次Jail,以避免出现某些莫名其妙的兼容性问题。
6. 一个小技巧: 每次在Jail中使用ps时,系统都会提示缺少/var/run/dev.db文件。这个问题很容易解决——将宿主机的/var/run/dev.db复制到$jailhome/var/run/下即可,此后就不会再出现这个烦人的提示了。
