6.4 Securing BINDAn installation of BIND in which you can feel confident requires quite a bit of work, regarding both how the daemon runs and how its configuration files deal with communication. 6.4.1 Making Sense out of BIND VersionsThree major versions of BIND are presently in use, despite the ISC's best efforts to retire at least one of them. BIND v9 is the newest version and its current minor-version number is, as of this writing, 9.2.1. For a variety of practical and historical reasons, however, the BIND user community and most Unix vendors/packagers have been slow to embrace BIND v9, so BIND v8 is still in widespread use. Due to two nasty buffer-overflow vulnerabilities in BIND v8 that can lead to root compromise, it is essential that anyone using BIND v8 use its latest version, currently 8.2.5, or better still, upgrade to BIND v9, which shares no code with BIND v8 or earlier. Speaking of earlier versions, although BIND v.8.1 was released in May 1997, many users continue using BIND v4. In fact, some Unix vendors and packagers (e.g., OpenBSD[1]) still bundle BIND v4 with their operating systems. This is due mainly to stability problems and security issues with BIND v8 and mistrust of BIND v9. Accordingly, the Internet Software Consortium has continued to support and patch Version 4, even correcting the aforementioned buffer overflows in BIND v4.9.8 despite having announced earlier that BIND v.4 was obsolete.
Thus, BIND v.4 has remained in use well past what its creators (mainly Paul Vixie of the ISC) probably considered its useful lifespan. In my opinion, however, BIND v8's support for transaction signatures, its ability to be run chrooted, and its flags for running it as an unprivileged user and group (all of which we'll discuss shortly) far outweigh whatever bugginess remains in it. Furthermore, BIND v9 already appears to be both stable and secure (at least as much as BIND v4, that is) and is a complete rewrite of BIND. To date, there has been only one security problem in BIND v9, a denial-of-service opportunity in v9.2.0, but no remote-root vulnerabilities have been discovered yet. Therefore, if you use BIND, I highly recommend you run the latest version of BIND v9. Being "rewritten from scratch" and supporting for DNSSEC and other security features have potentially but credibly advanced BIND's trustworthiness. 6.4.2 Obtaining and Installing BINDShould you use a precompiled binary distribution (e.g., RPM, tgz, etc.), or should you compile BIND from source? For most users, it's perfectly acceptable to use a binary distribution, provided it comes from a trusted source. Virtually all Unix variants include BIND with their "stock" installations; just be sure to verify that you've indeed got the latest version. If you're not already familiar with your Linux distribution's "updates" web page, now's the time to visit it. BIND is one of the essential packages of which most distributions maintain current versions at all times (i.e., without waiting for a major release of their entire distribution before repackaging). The command to check the version number of your installed BIND package with Red Hat Package Manager is: rpm -q -v package-name if the package has already been installed, or: rpm -q -v -p /path/to/package.rpm if you have a package file but it hasn't been installed yet. The rpm package name for BIND is usually bind8 or bind. If you perform this query and learn that you have an old (pre-8.2.5 version), most package formats support an "upgrade" feature. Simply download a more current package from your Linux distribution's web site and upgrade it using your package manager. To do this with rpm, the command syntax is as follows (assuming you don't need special install options.): rpm -U /path/to/package.rpm If the previous syntax doesn't work, you can try this: rpm -U --force /path/to/package.rpm If you can't find a suitable binary distribution, compile it from source — just make sure you have gcc and the customary assortment of libraries. In BIND v8, simply follow the brief instructions in the source's INSTALL file. For most users, the sequence of commands is as follows: make depend make all make install If you want BIND installed in a custom location, then before compiling, add the following line to the Makefile.set file in your architecture's port directory of the BIND source tree (e.g., src/port/linux/Makefile.set): 'DESTDIR=/path/to/installation_root' Be sure to include the quotation marks and substitute /path/to/installation_root with the absolute path of the directory in which you want BIND v8 installed. Makefile.set also contains additional variables that define where individual components of BIND will be installed. Refer to the BIND v8 INSTALL file for more information about these variables. BIND v9's build instructions are in its source's README file. The usual sequence of commands to build BIND v9 is as follows: ./configure make make install If you wish to specify a custom installation directory for BIND v9, then use configure's -- prefix flag, e.g.: ./configure --prefix= /path/to/installation_root (where /path/to/installation_root is the absolute path of the directory in which you want to install BIND v9). After this script finishes, type make. After that finishes successfully, type make install. All BIND binaries and support files will be installed where you specified. 6.4.3 Preparing to Run BIND (or, Furnishing the Cell)BIND itself is installed, but we're not ready to fire up named quite yet. I've alluded to BIND's checkered past when it comes to security: common sense tells us that any program with a history of security problems is likely to be attacked. Therefore, isolating BIND from the rest of the system on which it runs is a good idea. One way to do this, which is explicitly supported in BIND Versions 8 and 9, is by changing named's root directory. If BIND thinks that root is some directory other than /, a prospective cracker would be trapped, for example, should she exploit some obscure buffer-overflow vulnerability that allows her to become named. If named is run with its root changed to /var/named, then a file that appears to named to reside in /etc will in fact reside in /var/named/etc. Someone who hijacks named won't see configuration files for the entire system; she'll only see the ones you've placed into /var/named/etc (i.e., files used only by named). The system utility we normally use to execute a process in a changed-root environment is chroot. Although this functionality is built into BIND, the changed/fake root directory we designate for named is called a chroot jail. Note that to minimize a cracker's ability to leave the chroot jail, we should also run named as an unprivileged user and group instead of named's default, root. This functionality is also built into BIND Versions 8 and 9. We want named to run without access to the full filesystem, so we must provision our padded cell with copies of everything named requires to do its job. This provisioning boils down to the following:
6.4.3.1 Provisioning a chroot jail for BIND v8Since we all speak Linux here, the simplest way to enumerate the steps for constructing a chroot jail is simply to list the script I use in order to provision my BIND v8 chroot jails (see Example 6-1). Example 6-1. Provisioning the chroot jail, BIND v8#! /bin/bash # (Change the above path if your bash binary lives elsewhere) # Commands to create BIND v8 chroot jail, adapted # from a script by Kyle Amon # (http://www.gnutec.com/~amonk) # YOU MUST BE ROOT TO RUN THIS SCRIPT! # First, define some paths. BINDJAIL is the root of BIND's # chroot jail. BINDJAIL = /var/named # BINDBIN is the directory in which named, rndc, and other BIND # executables reside BINDBIN = /usr/sbin # Second, create the chroot jail and its subdirectories mkdir -m 2750 -p $BINDJAIL/dev $BINDJAIL/etc mkdir -m 2750 -p $BINDJAIL/usr/local/libexec mkdir -m 2770 -p $BINDJAIL/var/run mkdir -m 2770 $BINDJAIL/var/log $BINDJAIL/var/tmp mkdir -m 2750 $BINDJAIL/master mkdir -m 2770 $BINDJAIL/slave $BINDJAIL/stubs # Third, create unprivileged user & group for named # (may already exist if you use SuSE or Mandrake, but # you should ensure that passwd entry uses # /bin/false rather than a real shell) echo "named:x:256: " >> /etc/group echo "named:x:256:256:BIND:$BINDJAIL:/bin/false" \ >> /etc/passwd # Fourth, change some permissions & ownerships chmod 2750 $BINDJAIL/usr $BINDJAIL/usr/local chmod 2750 $BINDJAIL/var chown -R root:named $BINDJAIL # Fifth, copy some necessary things into the jail # Next line may be omitted in most cases cp $BINDBIN/named $BINDJAIL # Remaining lines, however, usually necessary - # these are things BIND needs in the chroot jail in # order to work properly. cp $BINDBIN/named-xfer $BINDJAIL/usr/local/libexec cp $BINDBIN/ndc $BINDJAIL/ndc cp /etc/localtime $BINDJAIL/etc mknod $BINDJAIL/dev/null c 1 3 chmod 666 $BINDJAIL/dev/null Note that you should substitute /var/named with the full path of the directory you wish to designate as named's root (many people do use /var/named). Similarly, in the chown -R line, substitute named with the name of the group that should own /named/root (I recommend named or some other group devoted to BIND — i.e., a group that doesn't include any real users or other application accounts as members.) Additionally, /path/to/named_binary and /path/to/ndc_binary should be replaced with the path to named and ndc (both are usually installed in either /usr/local/sbin or /usr/sbin).
Example 6-1 can be used as a script with minimal customization — just be sure to edit the values for BINDJAIL and BINDBIN, if appropriate. There's still one more step that's too distribution-specific to be included in Example 6-1: tell syslogd to accept named's log data from a socket in the chroot jail. You could, of course, configure named to log instead directly to files within the chroot jail. Most users, however, will find it much more convenient to log some or all of their named events to syslog by adding an -a flag to their syslog startup script. For example, on my Red Hat Linux system, syslogd is started by the script /etc/rc.d/init.d/syslog. To tell syslogd on that system to accept log data from a named process running chrooted in /var/named, I changed the line: daemon syslogd -m 0 to read: daemon syslogd -m 0 -a /var/named/dev/log Note that to use ndc to control your chrooted named process, you'll first need to recompile ndc as a static binary, with the chroot path in the file src/bin/ndc/pathnames.h. To do this, perform the following steps:
From now on, you'll need to use the chroot command to invoke ndc, e.g.: chroot /path/to/chroot_jail ./ndc [ndc command] 6.4.3.2 Provisioning a chroot jail for BIND v9This process is similar for BIND v9, as shown in Example 6-2. Example 6-2. Provisioning the chroot jail, BIND v9#!/bin/bash # (Change the above path if your bash binary lives elsewhere) # # Commands to create BIND v9 chroot jail, adapted # from a script by Kyle Amon (http://www.gnutec.com/~amonk) # and from the Chroot-BIND-HOWTO (http://www.linuxdoc.org) # YOU MUST BE ROOT TO RUN THIS SCRIPT! # First, define some paths. BINDJAIL is the root of BIND's # chroot jail. BINDJAIL = /var/named # BINDBIN is the directory in which named, rndc, and other BIND # executables reside BINDBIN = /usr/sbin # Second, create the chroot jail and its subdirectories. mkdir -m 2750 -p $BINDJAIL/dev $BINDJAIL/etc mkdir -m 2770 -p $BINDJAIL/var/run mkdir -m 2770 $BINDJAIL/var/log $BINDJAIL/var/tmp mkdir -m 2750 $BINDJAIL/master mkdir -m 2770 $BINDJAIL/slave $BINDJAIL/stubs # Third, create unprivileged user & group for named # (may already exist if you use SuSE or Mandrake, but # you should ensure that passwd entry uses # /bin/false rather than a real shell) echo "named:x:256:" >> /etc/group echo "named:x:256:256:BIND:$BINDJAIL:/bin/false" \ >> /etc/passwd # Fourth, give named some control over its own volatile files chown -R root:named $BINDJAIL # Fifth, copy some necessary things into the jail # Next line may be omitted in most cases cp $BINDBIN/named $BINDJAIL # Remaining lines, however, usually necessary - # these are things BIND needs in the chroot jail in # order to work properly. cp /etc/localtime $BINDJAIL/etc mknod $BINDJAIL/dev/null c 1 3 chmod 666 $BINDJAIL/dev/null 6.4.3.3 Invoking namedSince we haven't yet actually secured any configuration or zone files, it's premature to start named to start serving up names. But while we're on the subject of running named in a chroot jail, let's discuss how to start invoking named so that it begins in the jail and stays there. This is achieved by using the following command-line flags:
The first flag, -u, causes named to run as the specified username (rather than as root). As mentioned earlier, if an attacker successfully hijacks and thus becomes the named process, it's better they become some unprivileged user and not root. If named is running chrooted, it will be much harder if not impossible for an attacker to "break out" of the chroot jail if named isn't running as root. Sadly, BIND v9 supports the -u flag only for Linux systems running kernel version 2.3.99-pre3 or later (i.e., Version 2.4, since the 2.3 kernels were all development versions and you should not use a development kernel on any production system). Hopefully, by the time this book hits the presses, the Linux 2.4 kernel code will have matured sufficiently for the more cautious among us to consider it securable. If you've been holding on to your 2.2 kernel on a given system due to its stability or your own inertia and you intend to use this system primarily as a BIND v9 nameserver, I recommend you upgrade it to the latest version of the 2.4 kernel. In my opinion it's extremely important to run any publicly accessible service as an unprivileged user, if at all possible. The -g option in BIND v8 causes named to run under the specified group name. This option has been dropped in BIND v9, since it would be unusual to run named, which has the privileges of a specified user, with the privileges of some group other than the specified user's. In other words, the group you chose when you created named's unprivileged user account is the group whose ID named runs under in BIND v9. And finally, the -t option changes (chroots) the root of all paths referenced by named. Note that when chrooting named, this new root is applied even before named.conf is read. Therefore, if you invoke named with the command: named -u named -g wheel -t /var/named -c /etc/named.conf then named will look for /var/named/etc/named.conf instead of /etc/named.conf. Oddly, it is not necessary to use the -c flag if you don't run named chrooted (and keep named.conf in /etc); it is necessary to use -c if you run named chrooted (regardless of where you keep named.conf). One would expect the chrooted named to automatically look in /chroot/path/etc for named.conf, but for some reason, it must be explicitly told to look in /etc if / isn't really /. The net effect of these flags (when used properly) is that named's permissions, environment, and even filesystem are severely limited. Should an unauthorized user somehow hijack named, instead of gaining root permissions, he'll gain the permissions of an unprivileged account. Furthermore, he'll see even less of the server's filesystem than an ordinary user can: directories connected to higher directory-tree nodes than the chroot point won't even exist from named's perspective. 6.4.4 Securing named.confRunning named in a padded cell is appropriately paranoid and admirable in itself. But that's just the beginning! BIND's configuration file, named.conf, has a large number of parameters that allow you to control named with a great deal of granularity. Consider the example named.conf file listed in Example 6-3. Example 6-3. An example named.conf file for external DNS server# By the way, comments in named.conf can look like this... // or like this... /* or like this. */ acl trustedslaves { 192.168.20.202; 192.168.10.30}; acl bozos { 10.10.1.17; 10.10.2.0/24; }; acl no_bozos { localhost; !bozos; }; options { directory "/"; listen-on { 192.168.100.254; }; recursion no; fetch-glue no; allow-transfer { trustedslaves; }; }; logging { channel seclog { file "var/log/sec.log" versions 5 size 1m; print-time yes; print-category yes; }; category xfer-out { seclog; }; category panic { seclog; }; category security { seclog; }; category insist { seclog; }; category response-checks { seclog; }; }; zone "coolfroods.ORG" { type master; file "master/coolfroods.hosts"; }; zone "0.0.127.in-addr.arpa" { type master; file "master/0.0.27.rev"; }; zone "100.168.192.in-addr.arpa" { type master; file "master/100.168.192.rev"; }; The hypothetical server whose configuration file is represented here is an external DNS server. Since its role is to provide information to the outside world about coolfroods.org's publicly accessible services, it has been configured without recursion. In fact, it has no "." zone entry (i.e., no pointer to a hints file), so it knows nothing about and cannot even learn about hosts not described in its local zone files. Transfers of its local zone databases are restricted by IP address to a group of trusted slave servers, and logging has been enabled for a variety of event types. So how do we do these and even more nifty things with named.conf?
6.4.4.1 acl{} sectionsAlthough optional, Access Control Lists (ACLs) provide a handy means of labeling groups of IP addresses and networks. And since we're careful, we definitely want to restrict certain actions and data by IP address. An ACL may be declared anywhere within named.conf, but since this file is parsed from top to bottom, each ACL must be declared before its first instance in a parameter. Thus, it makes sense to put ACL definitions at the top of named.conf. The format for these is shown in Example 6-4. Example 6-4. Access Control List formatacl acl_name { IPaddress; Networkaddress; acl_name; etc. }; The element list between the curly brackets can contain any combination of the following:
Any of these elements may be negated with a leading "!"; e.g., "!192.168.3.1" means "not 192.168.3..1." Just make sure you keep more specific elements in front of more inclusive elements, since ACL element lists are parsed left to right. For example, to specify "all addresses in the network 10.0.0.0/8 except 10.1.2.3," your element could look like this: {!10.1.2.3; 10.0.0.0/8; } but not like this: { 10.0.0.0/8; !10.1.2.3; } Each element listed between curly brackets must end with a semicolon, even when the brackets contain only one element. This excerpt from Example 6-3 shows ACLs with a variety of elements: acl bozos { 10.10.1.17; 10.10.2.0/24; }; acl no_bozos { localhost; !bozos; }; Each time named.conf is read in this example, the parser will substitute all instances of the words bozos and no_bozos with the contents of their ACL's respective element lists. 6.4.4.2 Global options: The options{} sectionThe next thing to add is a list of global options. Some of the parameters that are valid for this section can also be used in zone sections; be aware that if a given parameter appears both in options{} and in a zone section, the zone version will supercede the options{} setting. In other words, the zone-section values of such parameters are treated as exceptions to the corresponding global values. Here are some useful parameters that can be used in options{}:
6.4.4.3 LoggingIn addition to global options, you'll want to set some logging rules. By default, named doesn't log much more than a few startup messages (such as errors and zones loaded), which are sent to the syslog daemon (which in turn writes them to /var/log/messages or some other file). To log security events, zone transfers, etc., you need to add a logging{} section to named.conf. The logging{} section consists of two parts: one or more channel{} definitions that indicate places to send log information, followed by one or more category{} sections that assign each event type you wish to track to one or more channels. Channels usually point either to files or to the local syslog daemon. Categories must be chosen from a set of predefined event types. Channel definitions take the format displayed in Example 6-5. Example 6-5. Log-channel syntaxchannel channel-name { filename [ file-options-list ] | syslog syslog-facility | null ; [ print-time yes|no; ] [ print-category yes|no; ] [ print-severity yes|no; ] [ severity severity-level; ] }; The file referenced by filename is by default put in named's working directory, but a full path may be given. (This path is assumed to be relative to the chrooted directory, if applicable.) You may define how big the file may grow, as well as how many old copies to keep at any given time, with the size and versions file options, respectively. Note, however, that this file rotation isn't nearly as elegant as syslogd's; once a file reaches the specified size, named will simply stop writing to it (instead of saving it with a different name and creating a new file, like syslogd does). The file won't be "rotated out" of active use until the next time named is started, which is what the versions option really dictates: it specifies how many copies of the file to keep around based on the number of times named has been restarted, not on the sizes of the files. See Chapter 10 for better methods of rotating logs. If instead of filename you specify syslog and a syslog-type, the channel will send messages to the local syslogd process (or syslog-ng, if applicable), using the facility specified by syslog-facility. (For a list of these facilities with descriptions, see Chapter 10). By default, named uses the daemon facility for most of its post-startup messages. The options print-time, print-category, and print-severity specify whether each event's log entry should be preceded by time and date, category label, and severity label, respectively. The order in which you specify these doesn't matter: they will be printed in the order time/date, category, severity. It isn't worthwhile to specify a print time for syslog channels, since syslogd automatically prints a timestamp on all its entries. Finally, the severity option lets you specify the minimum severity level that named messages must have to be sent to the channel. severity-level can be any of the syslog "priorities" (also described in Chapter 10), with the exception of debug, which can be specified but must be followed by a numeric argument between 1 and 10 to indicate debug level. Here's another excerpt of Example 6-3 from the beginning of this section: logging { channel seclog { file "var/log/sec.log" versions 3 size 1m; print-time yes; print-category yes; }; Per this logging{} statement, event types that are directed to the channel seclog will write their entries to a log file named /var/log/sec.log (the leading / at the start of the path is implied, since earlier in this example named's working directory is defined as /). When this file grows to 1 MB in size, named will stop sending log data to this channel and thus to this file. Each time named is started, the current version of this file will be renamed — e.g., sec.log.1 to sec.log.2, sec.log.0 to sec.log.1, and sec.log to sec.log.0. Log entries written to this file will be preceded by date and category, but severity will be omitted. Category specifications are much simpler (see Example 6-6). Example 6-6. Log category syntaxcategory category-name { channel-list ; }; As with acl-element lists, the channellist is semicolon-delimited and must contain one or more channels defined in a prior channel{} statement. (If you wish, you can log each category's messages to multiple channels.) Table 6-1 shows a list of categories that are of particular interest from a security standpoint. For a complete description of all supported categories, see the BIND v8 Operator's Guide (BOG) or the BIND 9 Administrator Reference Manual (ARM).
The named.conf options we've looked at so far apply to all name servers, including caching-only name servers that aren't authoritative for any zones (i.e., aren't master, slave, nor even stub for anything), and are thus inherently simpler and easier to secure than other kinds of DNS servers. Few of the remaining named.conf options in this section apply when setting up a caching-only server.
6.4.4.4 zone{} sectionsThe last type of named.conf section we'll examine here is the zone{} section. Like options{}, there are many additional parameters besides those described here; see the BOG or ARM for more information. These are the three parameters most useful in improving zone-by-zone security:
All three of these parameters may be used in the options{} section, zone{} sections, or both, with zone-specific settings overriding global settings. 6.4.4.5 Split DNS and BIND v9At the beginning of the chapter, I alluded to enhanced support in BIND v9 for split DNS. This is achieved by the new view{} statement, which can be used in named.conf to associate multiple zone files with each zone name. In this way, different clients can be treated differently — e.g., external users receive one set of answers regarding a given name domain, and internal users receive different answers about the same domain.
The syntax of view{} statements is shown in Example 6-7. Example 6-7. Zone-view syntaxview "view-name" { match-clients { match-list; }; recursion yes|no; zone "domain.name" { // standard BIND 8/9 zone{} contents here }; // additional zones may be defined for this view as well }; The match-clients match list has the same format and built-in labels as the element lists described earlier in this chapter under Section 6.4.4.1. Nested zone{} statements are no different than ordinary standalone zone{} statements. Example 6-8 illustrates two views defined for a split DNS scenario in which internal users' queries are answered with complete zone information, but external users are served from a zone file containing a subset. Internal users may also query for information about an internal zone, intranet.ourorg.org, for which the DNS server won't answer any external queries. Example 6-8. Some example viewsview "inside" { // Our internal hosts are: match-clients { 192.168.100.0/24; }; // ...and for them we'll do recursive queries... recursion yes; // Here are the zones we'll serve for them: zone "ourorg.ORG" { type master; file "master/ourorg_int.hosts"; }; // Here's a subdomain that isn't searchable in any form by outsiders zone "intranet.ourorg.ORG" { type master; file "master/intranet.ourorg.hosts"; }; }; view "outside" { //Client view for "none of the above" match-clients { any; }; // We don't recurse for the general public recursion no; // Answer outside queries from a stripped-down zone file zone "ourorg.ORG" { type master; file "master/ourorg_ext.hosts"; }; }; As the comments in Example 6-8 imply, the view{} definition is parsed top to bottom: when a user's IP address is compared against the defined views, it will progress down the list until a match is found. 6.4.5 Zone File SecurityOur secure DNS service is trapped in its padded cell and very particular about what it says to whom; in other words, it's shaping up nicely. But what about the actual zone databases? The good news here is that since our options are considerably more limited than with named.conf, there's less to do. The bad news is that there's at least one type of resource record that's both obsolete and dangerous, and to be avoided by the security conscious. Example 6-9 shows a sample zone file for the hypothetical domain boneheads.com. Example 6-9. Sample zone file$TTL 86400 // Note: global/default TTL must be specified above. BIND v8 didn't check for this, // but BIND v9 does. @ IN SOA cootie.boneheads.com. hostmaster.boneheads.com. ( 2000060215 ; serial 10800 ; refresh (3H) 1800 ; retry (30m) 120960 ; expiry (2w) 43200 ) ; RR TTL (12H) IN NS ns.otherdomain.com. IN NS cootie.boneheads.com. IN MX 5 cootie.boneheads.com. blorp IN A 10.13.13.4 cootie IN A 10.13.13.252 cootie IN HINFO MS Windows NT 3.51, SP1 @ IN RP john.smith.boneheads.com. dumb.boneheads.com. dumb IN TXT "John Smith, 612/231-0000" The first thing to consider is the Start-of-Authority (SOA) record. In Example 6-9, the serial number follows the yyyymmdd## convention. This is both convenient and helps security since it reduces the chances of accidentally loading an old (obsolete) zone file — the serial number (2000060215 in Example 6-9) serves both as an index and as a timestamp. The refresh interval is set to 10,800 seconds (three hours). Other common values for this are 3,600 seconds (one hour) and 86,400 (one day). The shorter the refresh interval, the less time it will take for changes to the zone's records to propagate, but there will be a corresponding increase in DNS-related network traffic and system activity. The expiry interval is set to two weeks. This is the length of time the zone file will still be considered valid should the zone's master stop responding to refresh queries. There are two ways a paranoiac might view this parameter. On the one hand, a long value ensures that if the master server is bombarded with denial-of-service attacks over an extended period of time, its slaves will continue using cached zone data and the domain will still be reachable (except, presumably, for its main DNS server!). On the other hand, even in the case of such an attack, zone data may change, and sometimes old data causes more mischief than no data at all. Like the refresh interval, the Time To Live interval (TTL) should be short enough to facilitate reasonably speedy propagation of updated records but long enough to prevent bandwidth cluttering. The TTL determines how long individual zone's RRs may remain in the caches of other name servers who retrieve them via queries. Our other concerns in this zone file have to do with minimizing the unnecessary disclosure of information. First, we want to minimize address records (A-records) and aliases (CNAME records) in general, so that only those hosts who need to be are present. We need to use Responsible Person (RP) and TXT records judiciously, if at all, but we must never, ever put any meaningful data into an HINFO record. HINFO is a souvenir of simpler times: HINFO records are used to state the operating system, its version, and even hardware configuration of the hosts to which they refer! Back in the days when a large percentage of Internet nodes were in academic institutions and other open environments (and when computers were exotic and new), it seemed reasonable to advertise this information to one's users. Nowadays, HINFO has no valid use on public servers other than obfuscation (i.e., intentionally providing false information to would-be attackers). In short, don't use HINFO records! RP is used to provide the email address of someone who administers the domain. It's best to set this to as uninteresting an address as possible — e.g., [email protected] or [email protected]. Similarly, TXT records contain text messages that have traditionally provided additional contact information (phone numbers, etc.), but should be kept down to only necessary information or, better still, be omitted altogether. Returning to Example 6-5, we see that the last few records are unnecessary at best and a cracker's goldmine at worst. I repeat, if you feel you must use RP and TXT, carefully weigh the usefulness of doing so against the risk. And don't use HINFO at all. 6.4.6 Advanced BIND Security: TSIGS and DNSSECMost of the security controls we've examined so far in this chapter have involved limiting what data the DNS server provides and when. But what about authentication? For example, what's to stop an attacker from masquerading his host as a trusted master server for your domain and uploading bogus zone files to your slaves, using spoofed packets (i.e., with forged IP source addresses) to get past your ACLs? And what about data integrity: what's to stop such an attacker from using a "man-in-the-middle" attack to alter the content of legitimate DNS queries and replies? Fortunately, Transaction Signatures (TSIGs), which are described in RFC 2845 and were originally implemented in BIND 8.2, can provide authentication and some measure of data integrity to transactions between DNS servers. Unfortunately, TSIGs don't guarantee that DNS information hasn't been compromised prior to transmission. If an attacker successfully "roots" a DNS server or somehow acquires a copy of its TSIG, bogus DNS information can be signed. For several years, though, the IETF has been working on DNS Security Extensions (DNSSEC, described in RFC 2535 and other documents developed by the IETF's dnsext working group). This set of extensions to DNS (mainly in the form of new resource records for keys and signatures) provides a means of cryptographically signing and verifying DNS records themselves. Combining TSIG and DNSSEC functionality should make for much more trustworthy DNS on the Internet. However, DNSSEC is still a work in progress. Despite being mostly implemented in BIND v9, DNSSEC is a bit complicated and unwieldy as it stands today. Since BIND's TSIG functionality is more mature, easier to use, and supported in both BIND v8(.2+) and BIND v9, we'll end our discussion of BIND with a description of how to use TSIGs. If you're interested in the cutting edge of DNS security with DNSSEC (I hope that many people are, to help drive its development and eventual widespread adoption), I highly recommend Chapter 11 of Albitz and Liu's definitive DNS and BIND (O'Reilly). Anyone who's serious about DNS security should own the newest edition of this book. 6.4.6.1 Transaction Signatures (TSIGs)To use TSIGs to sign all zone transfers between a zone's master and slave, all you need to do is this:
Step 1 is most easily done with BIND's dnskeygen command. To create a 512-bit signing key that can be used by both master and slave, type the following: dnskeygen -H 512 -h -n keyname The output will be saved in two files named something like Kkeyname.+157+00000.key and Kkeyname.+157+00000.private. In this case, the key string in both files should be identical; it will look something like ff2342AGFASsdfsa55BSopiue/u2342LKJDJlkjVVVvfjweovzp2OIPOTXUEdss2jsdfAAlskj==. Steps 2 and 3 create entries in named.conf like those illustrated in Example 6-10. This must be done on each server, substituting keyname with whatever you wish to name the key — this string must be the same on both servers. Example 6-10. key{} and server{} syntaxkey keyname { algorithm hmac-md5; secret "insert key-string from either keyfile here"; } server IP address of remote server { transfer-format many-answers; # (send responses in batches rather than singly) keys { keyname; }; }; Even without a corresponding server{} statement, a key{} statement tells a DNS server to sign replies to any requests it receives that have been signed by the defined key. A server{} statement tells named to sign all requests and updates it sends to that server, using the specified key. Note that key{} statements must always precede any other statements that refer to them; e.g., server{} statements. I therefore recommend putting key{} statements at the top of your named.conf file, along with your ACL definitions. After you've created the key and added corresponding key{} and server{} statements to both hosts' named.conf files, all you need to do is restart named on both servers by issuing one of the following commands on both servers: kill -HUP, ndc restart (on BIND v8), or rndc restart (BIND v9). All subsequent zone data exchanged between these two servers will be cryptographically signed using the shared TSIG key. Unsigned or improperly signed zone data will be rejected. 6.4.6.2 Additional uses for TSIGsA key specified by a key{} statement in named.conf may also be used in acl{}, allow-transfer{}, allow-query{}, and allow-update{} statements in each statement's element list. This gives you much greater flexibility in building element lists and the statements that use them, and thus more granular control over named's behavior. It also provides a criterion besides IP source address for authenticating client requests, therefore mitigating BIND's exposure to IP-spoofing attacks. Example 6-11 shows a key{} definition followed by such an access control list. Example 6-11. A TSIG key in an access control listkey mon_key { algorithm hmac-md5; secret "ff2342AGFASsdfsa55BSopiue/u2342LKJDJlkjVVVvfjweovzp2OIPOTXUEdss2jsdfAAlskj=="; } acl goodmonkeys { 10.10.100.13; key mon_key ; }; An English translation of this acl is "the label goodmonkeys refers to the host with IP address 10.10.100.13 whose data is signed with the key mon_key." The key keyname ; syntax used in the acl's element list is the same whether used in an acl{} or in an allow-transfer|query|update{} statement. Suppose in the fictional named.conf file excerpted in Example 6-11 we see the following: allow-transfer { goodmonkeys; }; This statement, which could be nested in either an options{} statement or a zone{} statement (depending on whether it's global or zone specific), says that zone-transfer requests will only be honored if they match the acl goodmonkeys, i.e., only if the requests come from 10.10.100.13 and are signed with the key mon_key. 6.4.7 Sources of BIND (and IS Security) InformationThe guidelines and techniques we've covered here should give you a good start on securing your BIND server(s). For more in-depth understanding of these techniques, I strongly recommend you read the BIND v8 Operators' Guide and the BIND v9 Administrators' Reference Manual. For me at least, these are among the most useful documents provided in any OSS package. Another excellent source of BIND security information is Liu's "DNS Security" slideshow. Section 6.6 at the end of this chapter lists information about these and other BIND resources. Equally important, every BIND user should subscribe to at least one security-advisory email list. BUGTRAQ is my personal favorite, since it's both timely and inclusive (but it's also high volume; I recommend the digest version). See http://www.securityfocus.com/cgi-bin/subscribe. pl for an online subscription form. Another excellent list is VulnWatch, which has no digest but is much lower volume than BUGTRAQ. See http://www.vulnwatch.org/subscribe.html for more details. I also recommend that you look up and read the CERT advisories listed in Section 6.6 at the end of this chapter. Understanding past BIND vulnerabilities is essential to understanding BIND security. |