Section 6.4. Securing BIND

An 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 Versions

Three 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.

[1] In the case of OpenBSD, certain features of later BIND versions have been grafted into BIND v4.9.8 ? OpenBSD's version of BIND v4.9.8 appears to be a very different animal than the version archived on the ISC FTP site.

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 BIND

Should 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.

If you choose to install BIND in a nonstandard directory tree, I don't recommend that this be the same tree you intend to use as a chroot jail. (If you have no idea what this is, you may wish to read the first couple of paragraphs of the next section right now). In my opinion, one basic assumption when using a chroot jail is that BIND may be hijacked by an attacker; if so, you don't want that intruder altering or replacing BIND's libraries or binaries. In short, you probably shouldn't keep all your BIND eggs in one basket (or directory tree, as it were).

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:

  1. Creating a scaled-down replica of our "real" root filesystem (e.g., /etc, /bin, /sbin, /var, etc.)

  2. Copying a few things BIND will expect to see and use in that filesystem

  3. Setting appropriately paranoid ownership and permissions of these files and directories

6.4.3.1 Provisioning a chroot jail for BIND v8

Since 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).

ndc, BIND v8's Name Daemon Control interface, and its BIND v9 successor rndc (the Remote Name Daemon Control interface), can be used to control named: each is included with its respective BIND source code and binary distributions. Both commands are most often used for reloading zone files, but personally, I find it just as easy to do this with BIND's startup script, e.g., /etc/init.d/named reload.

Instructions follow on setting up ndc and rndc for chroot environments, but for information on general usage, see the ndc(8) or rndc(8) manpage.

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:

  1. cd to the root directory of your BIND v8 source code.

  2. Edit .settings to change the line containing gcc options (e.g., containing the string -CDEBUG=...), and add the flag -static to it.

  3. Edit bin/ndc/pathnames.h to change the path /var/run/ndc to /path/to/chroot_jail/ndc.

  4. Recompile and copy the new ndc binary to the root of your chroot jail.

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 v9

This 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 named

Since 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:

  • -u username

  • -g group name (BIND v8 only)

  • -t directory_to_change_root_to

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.conf

Running 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?

In general, named.conf in BIND v9 is backward-compatible with BIND v8; therefore, the following applies equally to both, except where noted otherwise.

6.4.4.1 acl{} sections

Although 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 format
acl acl_name { IPaddress; Networkaddress; acl_name; etc. };

The element list between the curly brackets can contain any combination of the following:

IP host addresses

In the form x.x.x.x, e.g., 192.168.3.1

IP network addresses

(BIND documentation calls these "IP prefixes") in the "CIDR" form x.x.x.x/y (e.g., 172.33.0.0/16)

Names of ACLs

Defined in other acl{} sections, including the built-in ACLs "any," "none," "localhost," and "localnets"

Key-names

Defined earlier in named.conf in key{} statements

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{} section

The 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{}:

listen-on [port#] { list of local interface IPs ; };

Specify on which interface(s) to listen for DNS queries and zone-transfer requests. This and all other address lists enclosed in {} must be separated with semicolons. Port number is optional (default is 53).

listen-on [port#] { any |none ; };

(BIND v9 only.) Specify whether to listen on all interfaces with an IPv6 address.

allow-recursion { list of IP addr's/nets ; };

Perform recursive queries for a specified IP list, which can consist simply of the word none;.

allow-transfer { list of IP addr's/nets, or "none" ; };

Specify which addresses and/or networks may receive zone transfers, should they ask for one.

allow-query { IP/acl-list ; };

Allow simple DNS queries from these IPs/ACLs/nets (or none).

version "[message]";

Display your version number. There's no legitimate reason for anyone but your own network administrators to know your BIND version number. Some people use this parameter to respond to version queries with bogus or humorous information.

recursion [yes |no];

Turn recursion on or off globally. If off, set fetch-glue to no as well (see next item in this list).

fetch-glue [yes |no];

Permitted but unnecessary in BIND v9. Setting this to no will prevent your name server from resolving and caching the IPs of other name servers it encounters. While glue-fetching makes for more readable logs, it's also allowed some clever cache-poisoning attacks over the years. In BIND v8, glue records will be fetched in the course of normal queries unless you disable it here. In BIND v9 glue records are never fetched, regardless of whether you set this option.

6.4.4.3 Logging

In 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 syntax
channel 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 syntax
category 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).

Table 6-1. Logging categories related to security

Category name

Supported in BIND v8

Supported in BIND v9

Subject of messages

default

Messages of any category not assigned to a channel; if no channels are specified for default, then default's messages will be sent to the built-in channels default_syslog and default_debug

config

Results of parsing and processing named.conf

security

Failed and successful transactions

xfer-in

Inbound zone transfers (i.e., from locally originated zone requests)

xfer-out

Outbound zone transfers (i.e., from externally originated zone requests)

load

 

Loading of zone files

os

 

Operating system problems

insist

 

Failures of internal consistency checks

panic

 

Unexpected shutdowns (crashes)

maintenance

 

Routine self-maintenance activities

general

 

Uncategorized messages

client

 

Client requests

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.

The main vulnerability on caching servers is cache poisoning. The best defense against cache poisoning (in addition to running the very latest version of your DNS software) is judicious use of the global options allow-recursion{}, allow-query{}, fetch-glue, and recursion. On a caching-only server recursion must be set to yes, since recursion is its primary role, so be sure to restrict on which hosts' behalf recursion is performed using the allow-recursion{} directive.

6.4.4.4 zone{} sections

The 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:

allow-update { element-list ; };

Allow Dynamic DNS updates from the hosts/networks specified in the element list. The element list may contain any combination of IP addresses, IP networks, or ACL names. (All referenced ACLs must be defined elsewhere in named.conf.)

allow-query { element-list ; };

Allow DNS queries from these entities.

allow-transfer { element-list ; };

Respond to requests for zone transfers from these entities.

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 v9

At 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.

If you use view{} functionality for one zone, you must use it for all. Put another way, if even one view is defined, then all zone{} statements must be nested within view{} statements. Standalone (nonnested) zone{} statements may only be used in the complete absence of view{} statements.

The syntax of view{} statements is shown in Example 6-7.

Example 6-7. Zone-view syntax
view "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 views
view "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 Security

Our 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., information@wuzza.com or hostmaster@wuzza.com. 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 DNSSEC

Most 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:

  1. Create a key for the zone.

  2. On each server, create a key{} entry in named.conf containing the key.

  3. On each server, create a server{} entry in named.conf for the remote server that references the key declared in Step 2.

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{} syntax
key 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 TSIGs

A 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 list
key 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 follow