配置一个安全的chroot DNS

  已经在FreeBSD上使用了bind,但没有chroot,最近可能会添置服务器,因此这些天开始收集一下chroot、冗余备份方面的资料,回头重新设计、安装,DNS可是重中之重的服务器啊,马虎不得。

  由于网络安全最近使用的比较多的是Red Hat 操作系统,而且自己也是在一台Red Hat 9 下面配置好Bind 9的,因此在下面的例子中就以Red Hat AS3为配置平台进行介绍。网络安全所用服务DNS是solaris操作系统,因此写shell的时候,我争取做到对Solaris也通用。由于各种因素,我没能亲自在Solaris上进行测试。下面的安装过程对Red Hat和其他unix操作系统都没问题,最后附上的自动安装的shell脚本我只在Red Hat下测试成功,对Solaris大概也基本上顾及到了,如果大家在实际使用那个脚本的时候发现在Solaris下使用存在问题,请及时反馈到此文的留言中,以使我及时更正,方便其他读者朋友。

  首先,在开始以前,让我们解释一下标题中出现的chroot 和 bind这两个词。 先是chroot,事实上,在很多英文文章中,称它为”jail”( 监牢, 拘留所, 监狱)。那么什么是”Jail”呢?简单来说,就是把一个事物限制到某个范围。大家都知道,有时候由于一个应用程序的bug、漏洞等问题,会导致该程序被攻击者控制,取得相应用户的权限,进而取得系统管理员级别的权限。例如Windows用户对一些iis漏洞导致系统最高权限落入攻击者之手的事情肯定时有耳闻。不管什么程序,都可能有bug/漏洞,为了防止这样系统中某程序的漏洞导致系统最高权限被攻击者窃取的事件发生,就需要限制该程序的权限。

  所谓的限制,并不是不让该程序运行,而是对程序运行时候可以使用的系统资源、用户权限、所在目录进行严格控制。这样,在该程序被他人非法控制后,能具有的权限也相当有限,对系统也不会造成更大的危害。举个形象的例子,架过ftp 服务器的朋友都知道,用户凭借自己的用户名/密码可以进入到自己的空间内进行上传/下载/添加删除目录等操作权限,而对其他用户的目录和系统的其他目录无法进行任何操作(当然,这些权限是管理者合法授权的),这个就可以看做是一个Jail,把ftp用户限制在自己的目录里。在计算机界术语中,我们把这种对程序的Jail,特称为”chroot”。因此题目中的chroot bind,大家可以理解成“权限受严格限制的bind”。

  值得一提的是,chroot的程序并不能说是程序本身更安全了,它跟没有chroot的程序比较,依然有着同样多的bug/漏洞,依然会被攻击者利用这些bug/漏洞进行攻击并得逞。那么我们辛苦chroot是为了什么呢?是为了把损失降低到最小。打个比方,购买人生保险并不能保你一生平安,但是可以在你遇到麻烦的时候让损失少一些。我们的chroot程序也是同样的道理,当攻击者取得了该程序的权限的时候,由于程序本身的权限被严格限制了,因此攻击者无法造成更大的破坏,也无法夺取操作系统的最高权限。DNS 服务器由于是作域名解析之用,需要应付来自各地的很多访问,且一般不限制来访ip,因此安全隐患和被攻击的可能性相当大。做一个DNS服务器的资料网络上到处可寻,而作为网络管理员,我们需要的除了域名解析,还有“安全”(网络安全在以前的网管笔记中提到过,“安全是一种意识”,在做任何事情的时候都提醒自己注意安全,是一个称职的网络管理者所应该具备的)!

  好了,接下来是第二个名词“Bind”。Bind 是ISC 公司的软件,而它也是目前世界上使用最普遍、最通用的DNS软件,如果说Apache和IIS是两分Web Server天下的话,那么Bind 不折不扣是DNS Server事实上的标准了。

  接下来开始步入正题,开始我们的Bind安装之旅。chroot方式安装软件,事实上是把一个软件整体限制到根目录下的一个子目录中。即该软件只在此目录内具有权限,而一旦跳出该目录就无任何权限了。在Bind 8的时候,想要把Bind的全部文件放到一个目录下是一件很麻烦的事,而到了Bind 9开发公司ISC终于顺应民心,不光让Bind 软件能方便地安装到同一目录下进行权限限制作业,而且连远程控制软件也加上了,真是超值奉送(稍安勿燥,后面会详细介绍)。

  向耐心看到这里的朋友致敬,下面我们立即开始正题:

  1、安装Bind 9

  虽然我所用的Red Hat AS3 中有rpm包,但是为了方便其他操作系统的朋友,我们还是从源代码包方式安装。首先从ISC公司的主页(http://www.isc.org/products/BIND/)下Bind 9 软件包。

  wget ftp://ftp.isc.org/isc/bind9/9.2.3/bind-9.2.3.tar.gz

  (我没下最新的,下的是稳定版,您可以根据自己的需要选取)

  接着开始解压缩(为描述简单,以下操作如无特殊声明,都是以root权限进行)
  tar vzxf bind-9.2.3.tar.gz

  卸载Red Hat 中原有的Bind,一共有三个rpm包
  rpm -e bind bind-utils caching-nameserver

  进入该目录开始编译安装
  ./configure –prefix=/usr/local –disable-ipv6 –disable-threads

  #因为ipv6和线程方式我用不到就去掉了,把Bind 9安装到/usr/local下
  make;make install

  到此Bind 9已经安装完成了,普通的 DNS Server 到此就安装结束了,而对我们的chroot 而言才刚开始呢。

  2、构建chroot 目录环境

  a.创建Bind 工作目录/chroot/named及下属工作目录

CODE:
  rm -rf /chroot/named #删除原来的旧目录,之所以加这句是我写shell的时候调试方便

  mkdir -p /chroot/named
  cd /chroot/named
  mkdir dev (虚拟/dev)
  mkdir etc (虚拟/etc)
  mkdir logs (存放日志)
  mkdir -p var/run (将来会在这下面放一个named.pid文件)


  b.建立Bind的组和用户named

CODE:
  groupadd named
  useradd -g named -d /chroot/named -s /bin/true named
  pASswd -l named #-l ,Lock,表示锁定用户


  c.创建虚拟设备(dev),日志记录的时候有的选项可能用到它们。在默认情况下,是使用/dev目录下的文件,但是由于我们需要把DNS限制到一个目录,所以必须完全把/dev下用到的文件(或者说设备)模拟过来才可以。

CODE:
   ls -lL /dev/zero /dev/null /dev/random


  看到类似

CODE:
  crw-rw-rw-  1 root   root    1,  3 2003-09-15 /dev/null
  crw-r–r–  1 root   root    1,  8 2003-09-15 /dev/random
  crw-rw-rw-  1 root   root    1,  5 2003-09-15 /dev/zero


  这样的,将其中的1,3这样的数字记录下来,这表示主设备号和次设备号(一般来说主设备号用来区分设备的种类;次设备号则是为了作唯一性区分,标明不同属性——注意,在unix系统中是把设备也当作文件来对待的),在redhat 9下,ls加不加-L参数都无所谓,但是在Solaris下则一定要加上才可以显示。

CODE:
  mknod dev/null c 1 3
  mknod dev/zero c 1 5
  mknod dev/random c 1 8


  d.复制时钟文件到我们chroot的etc下,Linux 的时钟设置文件为:/etc/localtime ,实际上这个文件是 /usr/share/zoneinfo 目录下对应文件的符号连接。(假设我们所处的地区位于上海,那么只要运行以下的命令就可以设置时区了。 ln -sf /usr/share/zoneinfo/ASia/Shanghai /etc/localtime;注意在天缘用的solaris 2.6中并没有此文件,而是该用/usr/share/lib/zoneinfo/GB)

  cp /etc/localtime etc/

  3、创建和设置BIND 9配置文件

  默认情形下,bind以/etc/named.conf文件为配置文件。但由于我们这里是要做chroot的DNS,因此需要把named.conf放到/chroot/named/conf下去,然后再做一个符号连接到/etc/named.conf。首先创建并编辑named.conf文件(由于介绍DNS的文章大多对named.conf的配置解释得相当详细,因此我就不一句句解释了,大家结合注释,参考其他文章看看,很容易理解的)

CODE:
vi /chroot/named/etc/named.conf,输入以下内容(由于每个人的配置都不同,所以天缘在这里只列出一个做cache only的DNS的设置)

options {
     //注意,由于是chroot方式,所以以下的/conf、/var并不是系统中真正的/conf和/var目录,而是指/chroot/named下的同名目录,此配置文件中所有地方都如此
directory    “/conf”; //配置文件所在目录
pid-file    ”/var/run/named.pid”; //进程守护文件
statistics-file “/var/run/named.stats”; //状态输出文件;在rndc中用到
dump-file    “/var/run/named.db”; //输出数据库文件,在rndc中用到

//隐藏真实版本号,我这里写个4.0作刻意误导
version     “[4.0]”;

     logging { //日志记录
        channel LAMER_log {
         file “/logs/DNS-lamer.log” versions 3 size 10m;
         severity info;
         print-severity yes; print-time yes;
         };

        channel SEC_log {
        file “/logs/DNS-sec.log” versions 3  size 10m;
        severity info;
        print-severity yes; print-time yes;
        };

        channel STAT_log {
        file “/logs/DNS-stat.log” versions 3 size 10m;
        severity info;
        print-severity yes; print-time yes;
         };

category cname { null; };
category lame-servers { LAMER_log; };
category security { SEC_log; };
category statistics { STAT_log; };
};

//根解析
zone “.” {
type  hint;
file  “named.root”;
};

// localhost 解析
zone “localhost” {
type  mASter;
file  “named.localhost”;
notify no;
};

// localhost 反向解析
zone  ”0.0.127.in-addr.arpa” {
type  mASter;
file  “named.127.0.0”;
notify no;
};


  之后进行符号连接到/etc目录下
  ln -s /chroot/named/etc/named.conf /etc/named.conf

  好了,接下来,当然就是设置named.root、named.local、named.127.0.0三个文件了,注意,这三个文件的真实位置是在/chroot/named/conf下哦。

  首先是named.root的建立

  dig @a.root-servers.net . ns > /chroot/named/conf/named.root #这是在redhat下的用法,因为天缘所用的solaris默认(我用的2.6)没有dig命令,所以在solaris下我们用

CODE:
cd /chroot/named/conf
ftp ftp://ftp.rs.internic.net/domain/named.root
接着是named.local

;
;named.local
;
$TTL  86400

@    IN SOA  @ root (
            42       ; 版本
            3H       ; 刷新时间3小时
            15M       ; 重试时间15分钟
            1W       ; 最大期限一周
            1D )      ; 最小TTL一天     IN NS    @
    IN A     127.0.0.1

接着是named.127.0.0

;
; named.127.0.0
;
$TTL  86400
@    IN   SOA   localhost. root.localhost. ( ;这里的root.localost其实是root@localhost,在DNS设置中,将@用.代替
              1 ; 版本
              28800   ; 刷新,这里和下面的特意以分钟为单位,和上面的写法不同,就当多举个例子吧
              14400   ; 重试
              3600000  ; 最大期限
              86400 )  ; 最小TTL
    IN   NS   localhost.
1    IN   PTR   localhost.


  4、设置权限

  其实这一步,才是我们作任何chroot 服务真正精华的地方。如何把权限划分得准确,不至于无法执行服务,也不能大到会威胁到其他程序的安全,实在是一个需要仔细考虑的问题。

  a.由于我们的目的是达到让bind程序以named用户身份运行,所以必须让它具有读配置,无写配置文件的权限,而且最好其他程序也不能改变我们的配置文件,只有root能改,named用户能读。为了满足这个要求的,自然就想到把文件的拥有者改为root,组用户设置为named,然后再慢慢仔细划分权限。

  cd /chroot/named

  chown –R root.named ./ #-R参数表示下属目录也依照此权限,-R参数在chown和chmod中经常用到

  b.接下来想想各个文件、子目录的权限。Root组对 文件需要读写执行权限,named组对文件需要读取权限,而对下属子目录而言,则必须具有执行权限才能进入其中。因此作以下权限设置。

  # 对文件赋予root 读写权限,赋予组named读权限
  find . -type f -print | xargs chmod u=rw,og=r

  # 对目录赋予roo读写执行权限,赋予组named读执行权限(这里的执行是为了能进入到下级目录中)
  find . -type d -print | xargs chmod u=rwx,og=rx

  #对etc目录下的配置文件,能不让其他用户读取当然是最好的,因此设置
  chmod o= etc/*.conf

  # “secondaries” 子目录是此DNS服务器作辅助服务器,从主服务器更新消息的时候需要的,会在里面创建一些新文件。因此它的权限也需要特别设置,在这个目录下,named组、用户需要具有读权限,而不需要用到root用户,也不想让其他的用户身份访问。

  chown root.named conf/secondaries/ #设置secondaries目录用户为root,组为named

  chmod ug=rwx,o= conf/secondaries/ #给予root和named全部权限,以方便访问下面的文件

  touch conf/secondaries/.empty # 去掉旧有的该文件

  find conf/secondaries/ -type f -print | xargs chown named.named #将用户组、用户都设置为named

  find conf/secondaries/ -type f -print | xargs chmod ug=r,o= #只让named组和用户有读权限,而其他用户无任何权限

  接着是为var/目录设置权限(在这里会生成进程守护文件named.pid—我们在named.conf中设置了的)

  chown root.root var/ #这里可以把named抛弃,

  chmod u=rwx,og=x var/ #root可以读写设置,其他用户能执行就行,其实我们之所以做一个chroot DNS需要大费周折地单独设置目录,就是为了不让named具有访问真正的/var的权限。

  chown root.named var/run/ #因为在run下面的需要由named身份来写named.pid文件,所以需要将组改为named好限制权限

  chmod ug=rwx,o=rx var/run/ #用户/组具有读写执行权限,其他用户能读/执行就可以了,这样的设置,主要是为了方便我们后面写shell来判断DNS目前的状态。

  chown root.named logs/ #日志目录,设置成这样的原因不用解释了吧

  chmod ug=rwx,o=rx logs/ #日志允许其他人看比较好,方便以后挂第三方程序

  5、启动并运行bind 9

  激动人心的时候到了,即将开始我们的处女bind 9运行了。

  运行以下命令

CODE:
/usr/local/sbin/named -t /chroot/named -u named -c /etc/named.conf


然后再ps –fCnamed 看看??如果出来类似

CODE:
UID    PID PPID C STIME TTY     TIME CMD
named  14023   1 0 May27 ?    00:00:00 /usr/local/sbin/named -t /chroot/named -u named -c /etc/named.conf


  这样的结果,说明成功了,恭喜你!!!

  每次都这样输入累不累??所以还是一起来写个shell吧(其实shell真的粉好用,但是一般真要系统地讲解起来又没有意思,所以我尽量在每篇文章中都把shell用上,慢慢就领会到它的好处咯)

CODE:
vi /chroot/named.start

#多cpu的记得加个 “-n ” 参数,才能启用多cpu哦,我也是在别的地方看到的
#
# named命令格式: named [-c 配置文件] [-d 除错级别] [-f|-g] [-n cpu个数]
#       [-p 端口-默认是53] [-s] [-t chroot目录] [-u 执行该命令的用户身份]

cd /chroot/named
#确保除错的debug文件能够以named身份写入
touch named.run #建立该文件
chown named.named named.run #设置文件拥有者为named.named
chmod ug=rw,o=r  named.run #权限为664
#以named身份,在chroot/named目录中以/etc/named.conf为配置文件执行named程序
#这里的/etc/named.conf是我们用ln –s连过去的,参看前文
/usr/local/sbin/named -t /chroot/named -u named -c /etc/named.conf


  然后以sh /chroot/named.start 执行此命令就行了,之后在/etc/resolv.conf为自己的DNS地址就可以测试了,当然,也可以用dig命令来查(个人觉得dig命令比nslookup好用,但是Solaris 2.6上默认没有)。

  6. 控制工具rndc的安装和使用

  rndc=remote dnc,以前装过bind 8的朋友都知道有个ndc工具,而在bind 9中,更是连远程控制的功能都加上了。说“加上”其实不够恰当,因为rdnc并不是用ndc改的,而是重新写的一个通过tcp协议进行DNS控制的软件。(有什么用?问问做虚拟主机/系统管理的朋友就知道,DNS一般都是用独立主机,如果可以远程reload配置文件,可以方便很多的)。 

  rndc 原本是应该读取/usr/local/etc/rndc.conf 作为配置文件的,但我们既然是安装chroot的DNS,所以有必要把rndc.conf转到/chroot/named/etc/rndc.conf。好,下面来看看我们的rndc.conf的写法。

CODE:
options {
    default-server localhost; //先配置本地的
    default-key   “rndckey”; //key的名字
};

server localhost {
    key   “rndckey”; //key的名字
};
include “/chroot/named/etc/rndc.key”; //在这个文件中包含了rndckey的值,之所以这样,是因为在rndc.conf和named.conf中都用到这个值,用include方便写自动的shell一些

同样的道理,在/chroot/named/etc/named.conf的也加上需要的语句

controls {
    inet 127.0.0.1 allow { localhost; } keys { rndckey; }; //允许localhost连接
};
include “/etc/rndc.key”;


  接下来,就是要生成/chroot/named/etc/rndc.key 文件了,它是一个采用bASe-64编码加密的长字符串key,我们用DNSsec-keygen命令来生成它:

CODE:
  cd /chroot/named/etc
  /usr/local/sbin/DNSsec-keygen -a HMAC-MD5 -b 256 -n HOST rndc


  得到一个类似Krndc.+157+30481这样的返回值,这说明已经在当前目录下成功建立了Krndc.+157+30481.key和Krndc.+157+30481.private两个文件。

CODE:
cat Krndc.+157+30481.private 显示
Private-key-format: v1.2
Algorithm: 157 (HMAC_MD5)
Key: aoqaT1r9Oz29DIj3VPn6+teHcvBudGAc17qLM4nPOqA=


 在这里,Key后面的那串字符就是我们想要的key了,把它复制下来,然后删除临时文件

CODE:
rm Krndc.+157+30481.*
vi /chroot/named/etc/rndc.key

key “rndckey” {
    algorithm    “hmac-md5″;
    secret     ” aoqaT1r9Oz29DIj3VPn6+teHcvBudGAc17qLM4nPOqA=”;
};


  现在已经配置完成了,建立2个软连接

CODE:
ln -s /chroot/named/etc/rndc.conf /usr/local/etc/rndc.conf
ln -s /chroot/named/etc/rndc.conf /etc/rndc.conf
Ok,接下来对目前运行的named飞起一腿,强迫让它重新读配置文件
 ps –fCnamed
 UID    PID PPID C STIME TTY     TIME CMD
 named  14023   1 0 May27 ?    00:00:00 /usr/local/sbin/named -t /chroot/named -u named -c /etc/named.conf

kill -1 14023    #14023 是named的进程号,至于-1的作用,自己man kill吧
现在来看看我们的rndc是否工作正常
/usr/local/sbin/rndc status 如果现实类似下面的情形就恭喜你咯
number of zones: 2
debug level: 0
xfers running: 0
xfers deferred: 0
soa queries in progress: 0
query logging is OFF
server is up and running


  如果出现的是rndc: send remote authenticator: permission denied 则肯定上面的步骤中存在问题,请一步步检查吧。

  7、开机执行脚本

  每次输入很长的命令来进行重新启动,停止DNS实在不是明智的做法,写个shell脚本来帮助我们吧。

CODE:
vi /etc/init.d/named

#!/bin/sh
#
export PATH=/usr/local/sbin:$PATH    # 把路径加进去,就不用每次输入全路径了

cASe “$1” in
 start)
    # 运行Bind
    echo -n “Starting named: ”
    sh /chroot/named.start
    echo “done”
    ;;
 stop)
    # 停止 Bind
    echo -n “Shutting down named: ”
    rndc stop
    echo “done”
    ;;
 reload)
    # 重新载入配置
    echo -n “Reload named: ”
    rndc reload
    echo “done”
    ;;
status)
    # 显示当前状态
    rndc status
    ;;
 *)
    echo “/etc/init.d/named {start|stop|status|reload}”
    exit 1
esac

exit 0


  然后当然是 chmod a+x /etc/init.d/named

  接下来就是对各个启动模式做符号连接了Red Hat 下:

CODE:
ln -s /etc/init.d/named /etc/rc2.d/S45named
 ln -s /etc/init.d/named /etc/rc3.d/S45named
ln -s /etc/init.d/named /etc/rc5.d/S45named

Solaris 2.6 下:
 ln -s /etc/init.d/named /etc/rc3.d/S45named
 ln -s /etc/init.d/named /etc/rcS.d/S45named


  到这里,我们的chroot bind 9 可以说真正正式完工!!

本文作者天缘由网络安全攻防研究室(www.91ri.org)信息安全小组收集整理。