保护 DNS 和 BIND
在 SANS 学院最近的共识文件“如何消除十大最关键的互联网安全威胁”(www.sans.org/topten.htm)中,调查参与者报告的首要漏洞类别是 BIND 弱点。当然,BIND 是为大多数互联网 DNS 服务器提供支持的开源软件包。事实上,根据 SANS 的说法,超过 50% 的 BIND 安装容易受到众所周知的(并且在许多情况下是旧的)漏洞利用。
好消息是,掌握我即将描述的简单概念和技术,您可以快速轻松地增强 Linux(或其他 UNIX)DNS 服务器上 BIND 的安全性。由于我们这里的重点是安全性,如果您是 BIND 的绝对初学者,您可能希望首先开始阅读 BIND 在线文档(参见结尾的“结论”)或 Albitz 和 Liu 的著作 DNS and BIND 的前一两章。
话虽如此,让我们首先简要了解域名服务和 BIND 的工作原理。假设有人(图 1 中的 myhost.someisp.com)正在上网,并希望查看站点 http://www.wiremonkeys.org/。还假设该用户的机器配置为使用名称服务器“ns.isp.com”进行 DNS 查询。由于名称 www.wiremonkeys.org 对于 Web 查询及其响应将通过的路由器没有意义,因此用户的 Web 浏览器需要在尝试查询之前了解 www.wiremonkeys.org 的 IP 地址。
首先,“myhost”询问“ns”是否知道 IP 地址。由于 ns.someisp.com 不是 wiremonkeys.org 的权威服务器,并且最近没有与任何主机进行通信,因此它开始代表用户进行自己的查询。为了回答其他查询而进行一个或多个查询的过程称为 递归。
ns.someisp.com 开始其递归查询,方法是向“根名称服务器”询问对 zone wiremonkeys.org 具有权威性的某个主机的 IP 地址。(所有互联网 DNS 服务器都使用静态“提示”文件来识别大约十三个官方根名称服务器。此列表维护在 ftp.rs.internic.net/domain,称为 named.root。)在我们的示例中,ns 询问 E.ROOT-SERVERS.NET(一个实际的根服务器,当前的 IP 地址为 192.203.230.10),后者回复说 wiremonkeys.org 的 DNS 由“ns-wiremonkeys.wiremonkeys.org”处理,IP 地址为 55.100.55.100。
然后,ns 询问 ns-wiremonkeys www.wiremonkeys.org 的 IP 地址。ns-wiremonkeys 返回答案 (55.100.55.200),ns 将其转发回 myhost.someisp.com。最后,myhost 通过 HTTP 直接联系 55.100.55.200 并执行 Web 查询。
这是最常见的名称查找类型。它和其他单主机类型查找简称为“查询”;DNS 查询在 UDP 端口 53 上处理。
但是,并非所有 DNS 事务都涉及单主机查找。有时需要传输整个域名(区域)数据库:这称为区域传输,当您从 nslookup 实用程序发出 ls 命令或运行 dig 时,就会发生这种情况。但是,区域传输的主要目的是为了使对同一域具有权威性的名称服务器彼此保持同步(例如,用于“主服务器到从服务器”的更新)。区域传输在 TCP 端口 53 上处理。
我们将在此处讨论的最后一个通用 DNS 概念是缓存。名称服务器缓存所有本地区域文件(即,它们的提示文件以及它们对其具有权威性的所有区域信息),以及自上次启动以来它们执行的所有递归查询的结果。也就是说,几乎所有内容:每个资源记录 (RR) 都具有(或继承其区域文件的默认)生存时间设置。这些设置确定每个 RR 在刷新之前可以缓存多长时间。
当然,这只是为了充分理解和使用 BIND 所需学习内容的一小部分。我什至没有提到转发器或反向查找。希望这足以用于讨论 BIND 安全性。
DNS 安全性可以归结为两条格言:始终运行您选择的 DNS 软件包的最新版本,并且永远不要向陌生人提供不必要的信息或服务。换句话说,保持最新并吝啬!
这转化为许多具体技术。首先是限制甚至禁用递归。使用配置文件参数可以轻松地限制它;完全禁用递归可能有可能也可能不可能,具体取决于名称服务器的角色。
例如,如果服务器是一个“外部”DNS 服务器,其唯一目的是回答有关其组织的公共服务器的查询,则它没有理由执行非本地主机名的查找(这是递归的定义)。另一方面,如果服务器向局域网 (LAN) 上的最终用户提供 DNS 解析,则它肯定需要递归来自本地主机的查询,但可以配置为拒绝来自非本地地址的递归请求,如果不是 所有 请求。
限制 DNS 活动的另一种方法是使用拆分 DNS 服务(参见图 2)。拆分 DNS 是指维护每个本地域名(区域)的公共和私有数据库的做法。公共区域数据库包含尽可能少的内容:列出可公开访问的名称服务器的 NS 记录、列出外部 SMTP(电子邮件)网关的 MX 记录、公共 Web 服务器以及希望外界了解的其他主机。
私有区域数据库可能是公共区域数据库的超集,或者它可能包含某些类别或主机的完全不同的条目。例如,许多组织使用 Microsoft Exchange 服务器进行内部电子邮件,但维护完全独立的 SMTP 网关系统以接收来自外部世界的邮件。这有时实际上是组织的防火墙,或者可能是 DMZ 网络中的专用邮件服务器,该网络连接到防火墙但与内部网络分离。
这种架构的价值应该是显而易见的:SMTP 网关的泄露不会自动导致内部电子邮件暴露给外部人员。通常以这种方式拆分的其他服务是 WWW(它将公共 Web 数据与内联网数据分开)、FTP 以及几乎所有其他 TCP/IP 服务,对于这些服务,希望区分公共数据和私有数据。但是,DNS 可以说是最重要的拆分服务,因为大多数其他 TCP/IP 服务都依赖于它。
DNS 吝啬的另一方面是区域文件本身的内容。即使是公共区域数据库也可能包含超出其需要的信息。主机可能具有不必要的描述性名称(例如,您可能正在告诉错误的人哪个服务器做什么),或者可能提供了过多或过于细化的联系信息。有些组织甚至列出各个系统的硬件和软件名称和版本!对于潜在的黑客来说,这些信息几乎总是比他们的预期受众更有用。
维护最新的软件并及时了解已知的 DNS 漏洞至少与仔细考虑实际 DNS 数据一样重要。此外,这更容易:BIND 的最新版本始终可以从 ftp.isc.org 免费下载,并且有关 BIND 漏洞的信息不仅通过一个,而且通过多个邮件列表和新闻组传播(其中一些列在本文章末尾)。
实际上,DNS 安全性还有第三条格言,但它并非 DNS 所独有:花时间理解和 使用 您的软件(以及您的 DNS 注册提供商 - Network Solutions 和其他顶级域名注册商都提供多种更改请求安全选项,包括 PGP。确保您的提供商要求 至少 对您的区域的所有更改请求进行电子邮件验证!)。
截至撰写本文时,最新版本是 8.2.2,补丁级别 5。由于一个特别糟糕的缓冲区溢出问题,该问题可能导致对易受攻击的系统进行未经授权的 root 访问(所有较旧的 8.2 版本中的“NXT”错误,在 CERT 咨询 #CA-99-14 中描述),因此 至关重要 的是,任何使用 BIND 的人都至少应使用版本 8.2.2P5。
请注意,在 1997 年 5 月最初发布 BIND v.8.1 之后的一段时间内,许多用户故意继续使用 BIND v.4,因为其稳定性(并且可能为了推迟学习新的配置文件语法)。事实上,互联网软件联盟 (ISC) 继续支持甚至修补版本 4,在 8.1 首次亮相整整一年后发布了 BIND v.4.9.7。
但是,ISC 不再建议任何人继续使用 BIND v.4,即使是 v.4.9.7。因此,有必要重申:在 Internet 服务器上运行 BIND 的每个人都应该运行版本 8 的最新版本。x。
我们已经确定您需要最新版本的 BIND。但是您应该使用预编译的二进制发行版(例如 RPM),还是应该从源代码编译它?对于大多数用户来说,使用二进制发行版是完全可以接受的,前提是它来自可信来源。实际上,所有 UNIX 变体都将 BIND 包含在其“库存”安装中;只需确保验证您确实拥有最新版本。
使用 Red Hat Package Manager 执行此操作的命令是 rpm -q -v bind8(如果软件包已安装),或者 rpm -q -v -p ./<软件包的路径和文件名>(如果您有软件包文件,但尚未安装)。BIND 的 rpm 软件包名称通常为“bind8”或“bind”。
如果您执行此查询并得知您拥有旧版本(pre-8.2.2p5 版本),则大多数软件包格式都支持“升级”功能;只需从 Linux 发行版网站下载更新的软件包版本,然后使用软件包管理器升级它。要使用 RPM 执行此操作,命令语法为 rpm -U ./<软件包的路径和文件名>,假设您不需要特殊的安装选项。如果上述方法不起作用,您可以尝试 rpm -U --force ./<软件包的路径和文件名>。
如果您找不到合适的二进制发行版,则从源代码编译它很容易:没有要运行的“configure”脚本,并且不需要编辑 BIND v.8x 的任何 Makefile。只需按照源代码的 INSTALL 文件中的简短说明进行操作(make; make install 是大多数人需要做的全部事情)。
现在启动 named(BIND 的主进程)还为时过早。但是,您计划如何运行 named 将决定应如何配置它。因此,现在是讨论一些可以增强安全性的启动选项的好时机。
与所有 Internet 服务一样,最好在“带软垫的牢房”中运行 named,如果潜在的黑客利用了例如某些晦涩的缓冲区溢出漏洞,他们将被困在其中。三个标志使这个牢房易于实现:-u <用户名>、-g <组名> 和 -t <named 要 chroot 到的目录>。
第一个标志使 named 在指定的用户名下运行。第二个标志使 named 在指定的组名下运行,而第三个标志更改(“chroot”)named 引用的所有路径的根目录。请注意,当以 chrooted 方式运行 named 时,即使在读取 named.conf 之前 也会应用此新根目录。因此,例如,如果您使用 named -u named -g wheel -t /var/named 调用 named,则它将在 /var/named/etc 而不是 /etc 中查找 named.conf。也就是说,named.conf 的默认位置始终是 /etc,但如果 named chroot 到路径 /other/path,则 /etc 将转换为 /other/path/etc。
当正确使用这三个标志时,最终效果是 named 的权限、环境甚至文件系统都受到严格限制。如果未经授权的用户以某种方式劫持了 named,他们不会获得 root 权限(在 BIND v.8 之前,named 以 root 身份运行),而是获得非特权帐户的权限。此外,他们甚至会看到 比 普通用户可以看到的服务器文件系统更少:连接到高于 chroot 点的目录树节点的目录从 named 的角度来看甚至不存在。
在带软垫的牢房中运行 named 本身就很偏执和精英化。但这仅仅是个开始!BIND 8.x 的配置文件 named.conf 及其大量受支持的参数,使您可以非常精细地控制 named 的行为。
考虑图 3 所示的示例 named.conf 文件。
此处表示其配置文件的假设服务器是外部 DNS 服务器。由于其作用是向外界提供有关 coolfroods.org 可公开访问的服务的信息,因此它已配置为不进行递归。事实上,它没有“.”区域条目(即,没有指向提示文件的指针),因此它对本地区域文件中未描述的主机一无所知,甚至无法了解。其本地区域数据库的传输通过 IP 地址限制为一组受信任的从服务器,并且已为各种事件类型启用了日志记录。
那么,我们如何使用 named.conf 完成这些甚至更巧妙和偏执的事情呢?
尽管严格来说是可选的,但访问控制列表 (acl) 提供了一种方便的方法来标记 IP 地址和网络的组。由于我们很偏执,因此我们绝对希望通过 IP 地址限制某些操作和数据。
acl 可以在 named.conf 中的任何位置声明,但由于此文件是从上到下解析的,因此每个 acl 都必须在其参数中的第一个实例之前声明。因此,将 acl 定义放在 named.conf 的顶部是有意义的。
它们的格式很简单
acl acl_name { IPaddr1; IPaddr2; ...etc. };
请注意,IP 地址列表可以包含 x.x.x.x 形式的完整 IP 地址或 x.x.x/24、x.x/16 等形式的网络地址。现在,每次读取 named.conf 时,解析器都会将 acl 名称的所有实例(在其定义之后发生的实例)替换为其相应的 IP 地址列表。
接下来要添加的是全局选项列表。此部分有效的某些参数也可以在区域部分中使用;请注意,如果给定参数同时出现在 options{} 和区域部分中,则区域版本将取代全局设置,因为它适用于该区域。换句话说,此类参数的区域部分值被视为对相应全局值的例外。
列表 2 显示了一些可以在 options{} 中使用的有用参数。
除了全局选项之外,我们绝对希望设置一些日志记录规则。默认情况下,named 不会记录超出一些启动消息(例如错误和加载的区域)的内容,这些消息将发送到 syslog d<\#230>mon(后者又将其写入 /var/log/messages 或其他文件)。要记录安全事件、区域传输等,您需要向 named.conf 添加 logging{} 部分。
logging{} 部分由两部分组成:一个或多个 channel{} 定义(每个定义定义一个发送日志信息的位置),后跟一个或多个 category{} 部分(其中您希望跟踪的每个事件类型都分配了一个或多个通道)。通道通常指向文件或本地 syslog d<\#230>mon,而类别实际上是预定义的;也就是说,您从一组预定义的类别中进行选择,并在每种情况下指定如何处理来自该类别的事件消息。
通道定义采用以下格式:channel 通道名称 {{filename syslog syslog-type|null]; print-time [yes | no]; print-category[yes | no];};
请注意,默认情况下 filename 放置在 named 的工作目录中,但可以给出完整路径(假定该路径相对于 chrooted 目录,如果适用)。
类别规范要简单得多
category 类别名称 {通道列表 ; };
请注意,与 IP 地址列表一样,通道列表以分号分隔,并且必须包含在之前的 channel{} 语句中定义的通道。有关支持的类别的完整列表,请参阅 BIND 操作员指南 (BOG);只需说 xfer-out、security、load、os、insist、panic 和 maintenance 通常是注重安全的 DNS 管理员感兴趣的。
“仅缓存”名称服务器(对于任何区域都不是权威的,即既不是主服务器、从服务器,甚至也不是任何内容的存根)本质上比其他类型的 DNS 服务器更简单,因此更容易保护。设置仅缓存服务器时,以下内容很少适用。
我们将在此处检查的最后一种 named.conf 部分是 zone{} 部分。与 options{} 一样,除了下面描述的参数之外,还有许多其他参数;有关更多信息,请参阅 BOG。
以下三个参数最有助于提高逐区域安全性
allow-update { IP/acl-list ; }; allow-query IP/acl-ist ; }; allow-transfer IP/acl-list ; };
allow-update 列出可以为区域提交动态 DNS 更新的主机;allow-query 指定哪些主机甚至可以提交简单的 DNS 查询;allow-transfer 限制谁可以下载整个区域文件。请注意,所有这三个参数都可以在 zone{} 部分和/或 options{} 部分中使用,区域特定设置将覆盖全局设置。
我们安全的 DNS 服务,被困在带软垫的牢房中,并且对它对谁说什么非常挑剔,正在顺利进行。但是实际的区域数据库呢?
这里的好消息是,由于我们的选项比 named.conf 中的选项要有限得多,因此需要做的事情更少。坏消息是,至少有一种资源记录类型既过时又危险,并且注重安全的人必须避免使用。
以下是假设域名“boneheads.com”的示例区域文件(参见图 4)。
首先要考虑的是起始授权 (SOA) 记录。在上面的示例中,序列号遵循 yyyymmdd## 约定,这既方便又有助于安全性,因为它降低了意外加载旧(过时)区域文件的可能性 - 序列号既充当索引又充当时间戳。
刷新间隔设置为三小时,这是带宽节省和偏执之间的合理折衷方案。也就是说,刷新间隔越短,DNS 欺骗(缓存中毒)攻击造成的损害就越小,因为此类攻击传播的任何“错误记录”都将在每次刷新区域时得到纠正。
过期间隔设置为两周。这是区域文件仍被视为有效的时间长度,如果区域的主服务器停止响应刷新查询。偏执狂可能从两个方面看待此参数。一方面,较长的值可确保,如果主服务器在较长时间内受到拒绝服务攻击的轰炸,其从服务器将继续使用缓存的区域数据,并且该域将继续可访问(除非,大概除了其主 DNS 服务器!)。但另一方面,即使在这种攻击情况下,区域数据也可能会更改,有时旧数据比根本没有数据会造成更多的麻烦。
同样,生存时间间隔应足够短,以促进从攻击或损坏中合理快速地恢复,但应足够长,以防止带宽混乱。(TTL 确定单个区域的资源记录可以在通过查询检索它们的其他名称服务器的缓存中保留多长时间。)
我们在该区域文件中的其他担忧与最大程度地减少不必要的信息泄露有关。首先,我们希望尽可能减少别名(“A 记录”)和规范名称(“CNAME”),以便只有需要存在的主机存在。(实际上,我们想要拆分 DNS,但是当这不可行或不适用时,我们仍然应该尝试保持区域文件稀疏。)
其次,我们希望最大程度地减少(递归)胶水提取的发生。当请求的名称服务器 (NS) 记录包含名称时,会发生这种情况,该名称的 IP 地址(通过 A 记录)不存在于回答 NS 查询的服务器上。换句话说,如果服务器 X 知道 Y 是 WUZZA.com 域的权威服务器,但 X 实际上不知道 Y 的 IP 地址,生活可能会变得很奇怪:这种情况为 DNS 欺骗攻击铺平了道路。因此,如果您真的想消除所有递归(我希望您现在已经这样做了),请确保您的资源记录都不需要递归胶水提取,然后将“fetch-glue”选项设置为“no”。
最后,我们需要明智地使用 RP 和 TXT 记录(如果使用),但绝不能将任何有意义的数据放入 HINFO 记录中。RP 或负责人用于提供管理该域的人员的电子邮件地址。最好将其设置为尽可能不令人感兴趣的地址,例如“information@wuzza.com”或“hostmaster@wuzza.com”。同样,TXT 记录包含传统上提供其他联系信息(电话号码等)的文本消息,但应仅保持足够具体以有用,或者最好完全省略。
HINFO 是更简单时代的纪念品:HINFO 记录用于声明它们引用的主机的操作系统、其版本,甚至硬件配置!早在互联网节点的大部分都在学术机构和其他开放环境中的日子(以及计算机既新奇又新颖的时候),向用户宣传此信息似乎是合理的。如今,HINFO 在公共服务器上没有有效的用途,除了混淆(即,故意向潜在的攻击者提供虚假信息)。简而言之,不要使用 HINFO 记录!
然后,回到图 3,我们看到最后几个记录充其量是不必要的,最坏的情况是黑客的金矿。尽管我们认为 SOA 记录看起来不错,但紧随其后的 NS 记录指向完全位于另一个域上的主机 - 请记住,我们不喜欢胶水提取,如果这里是这种情况,我们可能需要为 ns.otherdomain.com 添加 A 记录。
哇!我们从几个重要的角度研究了 BIND 安全性,但我们甚至没有提到加密控件。实际上,安全 DNS 协议(“DNSSEC”,在 RFC 2535 中描述)值得单独写一篇文章。DNS 的这组扩展提供了一种对区域传输和查询事务进行加密签名的方法,包括安全交换所有必要的密钥数据。由于 DNSSEC 尚未得到广泛实施(实际上甚至在 BIND v.8x 中也不完全支持),因此我们将把我们对它的讨论限制为事务签名 (TSIG) 的使用。
假设您希望对区域的主服务器和从服务器之间的所有区域传输进行签名。您将执行以下操作
为区域创建一个密钥
在每台服务器上,在 named.conf 中创建一个包含密钥的 key{} 条目
在每台服务器上,在 named.conf 中为另一台服务器创建一个 server{} 条目,该条目引用步骤 (2) 中声明的密钥。
步骤一最容易使用 BIND 的 dnskeygen 命令完成。要创建一个可供主服务器和从服务器使用的 512 位签名密钥,请键入 dnskeygen -H 512 -h -n <desired_keyname>。输出将保存在两个文件中,文件名类似于 Kdesired_keyname.+157+00000.key 和 Kdesired_keyname.+157+00000.private。在这种情况下,两个文件中的密钥字符串应相同;它看起来类似于“ff2342AGFASsdfsa55BSopiue/-2342LKJDJlkjVVVvfjweovzp2OIPOTXUEdss2jsdfAAlskj==”。
步骤二和步骤三包括在每台服务器上的 named.conf 中创建类似于以下的条目(将下面的“desired_keyname”替换为您希望密钥命名的任何内容 - 此字符串在两台服务器上必须相同!)
key desired_keyname { algorithm hmac-md5; secret "<insert key-string from either keyfile here>"; } server <IP address of other server> { transfer-format many-answers; # (send responses in batches rather than singly) keys { desired_keyname; }; };
请注意,key{} 语句必须始终位于引用它们的任何其他语句(例如 server{} 语句)之前。放置密钥服务器语句的逻辑位置是在 options{} 和区域语句之间。
现在您需要做的就是在两台服务器上重新启动 named(通过 kill -HUP 或 ndc restart)。瞧! 您现在处于 DNS 安全性的前沿!
我们在此处介绍的指南和技术应该为您提供一个良好的开端,以保护您的 DNS 服务器。为了更深入地了解这些技术,我强烈建议使用 BIND 的在线版本的操作员指南(包含在大多数二进制发行版中,或可从 http://www.isc.org/ 单独获得)。这是任何 OSS 软件包中提供的最有用的文档之一。BIND 安全信息的另一个极好的来源是 Liu 的“DNS Security”幻灯片演示(可以从他那里以 PDF 格式获得 - 见下文)。
同样重要的是,每个 BIND 用户都应该订阅至少一个安全咨询电子邮件列表。CERT 是我个人的最爱,因为它足够及时有用,但音量足够低,不会造成麻烦。并且在您方便的时候,您应该查找并阅读下面列出的 CERT 咨询 - 了解威胁是良好安全性的重要组成部分。

Mick Bauer (mick@visi.com) 是 ENRGI 明尼阿波利斯分部的安全实践主管,ENRGI 是一家网络工程和咨询公司。自 1995 年以来,他一直是 Linux 的忠实拥护者,自 1997 年以来一直是 OpenBSD 的狂热者,特别乐于让这些尖端的操作系统在过时的垃圾硬件上运行。Mick 欢迎问题、评论和问候。