Domain Name System


The Domain Name System (DNS) is a hierarchical distributed database that implements a global naming scheme for resources available on the Internet. It provides the infrastructure for mapping domain names to IP addresses as well as key data used to interpret email addresses. When people access resources on the Internet, they typically do so by using names such as www.google.com and abuse@comcast.net. Their computers use DNS to translate these names into the IP addresses suitable for use with Internet protocols. Obviously, text names are far easier for people to work with than numbers. There's a reason you don't hear people say "Man, 66.35.250.151 has really gone downhill lately."

Domain Names and Resource Records

The DNS database is organized as a tree data structure, with a single root node at the top (see Figure 16-15 for a very simple example of such a tree). For the sake of clarity, this diagram omits some domains that would be necessary to make the database functional. Every node (and leaf) in the tree is called a domain, and a domain's child nodes are called its subdomains. Each domain has a label, which is a short text name such as com, mail, www, or food. A domain name is a series of labels, separated by dots, that uniquely identifies a node in the tree by tracing the full path from the specified domain to the root domain. For example, the domain name www.google.com specifies a domain labeled www that's a subdomain of google.com. The google.com domain is a subdomain of the com domain, and com is a subdomain of the root domain. The root domain has an empty label, which is usually omitted in casual discussion. In configuration files and technical discussions, however, it's usually represented by a trailing dotwww.google.com., for example.

Figure 16-15. DNS tree data structure


Each domain owns a set of zero or more resource records, which describe attributes of that domain. In general, you work with DNS by asking about a domain name. The response you get is a set of resource records owned by that domain name. Every resource record has five elements, described in Table 16-2.

Table 16-2. Resource Record Elements

Name

Description

Format

Owner

The domain that owns this resource record.

Domain name

Type

A code that identifies which type of resource record it is.

16-bit integer

Class

A code that identifies the protocol system this resource record belongs to. It's usually IN, for "Internet."

16-bit integer

TTL

The time to live for this record, specified in seconds. It's how long this resource record should be cached before it's purged.

32-bit integer

RDATA

The actual contents of the resource record. The way this content is encoded depends on the type and class of the resource record.

Set of bytes


Name Servers and Resolvers

Before you can understand how resource records are used in practice, you need a brief review of name servers and resolvers. The DNS database is distributed among thousands of systems around the world, which are called name servers. The responsibility for maintaining this vast database is divided among the thousands of administrators of these systems; each administrator is responsible for a small piece of the global namespace. To facilitate this division of labor, the domain namespace is split up into sections called zones.

The code responsible for querying DNS on behalf of user applications is called resolver code. It takes a request from a user, tough function such as gethostbyname(), and begins asking name servers it knows about to try to hunt down an authoritative resource record with the answer.

There are two basic kinds of name servers: recursive and nonrecursive. Nonrecursive name servers are the most straightforward. They answer questions only about the zones they are responsible for. They have all this information in memory, so they don't need to query the DNS infrastructure for further information. (Note that they also have some delegation and glue information memorized, which you learn about through the rest of this chapter). Nonrecursive name servers give you an authoritative answer or tell you to go ask someone else.

Recursive name servers are a different animal. If they don't know the answer to a query offhand, they take it upon themselves to go find the answer. If they are successful, they consolidate all the intermediate findings into a nice concise answer for the client.

There are also two kinds of resolvers. A fully functional resolver can interrogate DNS to hunt down answers to user questions. It knows what to do when a nonrecursive name server doesn't have the answer. A stub resolver, on the other hand, is quite comfortable letting a recursive name server do all the work. It just needs the IP address of a local friendly recursive name server, and it relies on that server to handle interrogating the world's name servers.

The process of querying DNS for a piece of information often involves making multiple queries to different name servers. To speed up this process, both name servers and resolvers can implement a domain name cache, which stores results of queries locally for limited time frames. In fact, quite a bit of the information stored in DNS is instructions on how caches should manage information.

Zones

When you take responsibility for a zone, you're expected to set up two or more authoritative name servers. These servers are the ultimate authority for your zone, and DNS servers and resolvers ask your servers when they need resource records from your zone. When a name server or resolver receives a resource record originating from an authoritative name server, it usually caches the resource record for a predetermined length of time. Over time, your zone information gets distributed and cached across the global DNS infrastructure. You control the details of how your zone's information should be cached and refreshed.

Zones are created by delegating subdomains. For every zone, there's a single domain that's the closest to the root node, which is the top node of the zone. Figure 16-16 shows an example of a namespace with zone partitions overlaid in gray. (Again, this simplified view omits some necessary details.) Look at the zone with a top node of neohapsis.com. At some point, the administrator of the com. zone delegated control of the neohapsis.com. subdomain to the neohapsis administrator. This means requests for any subdomain of neohapsis.com. are under the authoritative purview of the neohapsis.com. zone. You can see that the neohapsis administrator delegated lab.neohapsis.com. to another zone, which might be managed by the lab administrator.

Figure 16-16. Example DNS tree with zones


Resource Record Conventions

There are several different types of resource records, distinguished by their type codes. The most important types, and the general format of their associated RDATA elements, are listed in Table 16-3.

Table 16-3. Resource Record Types

Type

Description

RDATA Format (IN Class)

A

A host address

32-bit IP address

NS

An authoritative name server

Domain name

SOA

The start of authority record, which contains information about the zone

Multiple parameters, including an administrator, an e-mail address, a serial number, and parameters to control caching and synchronization

MX

A mail exchanger for the domain

Numeric preference value followed by a domain name

CNAME

The canonical name of the domain

Domain name

PTR

A pointer to another domain

Domain name


The top node of any zone is a special node containing meta-information about that zone. It has two key sets of information: the SOA resource record for the zone and authoritative NS resource records for the zone. The SOA record contains information about caching parameters used by all the zone's resource records. The NS records authoritatively state the name servers in charge of the zone.

The A resource records are used liberally to assign IP addresses to domain names and can appear in any domain in the zone. CNAME records are used for aliases. If the domain name sol.lab.neohapsis.com is an alias to jm.lab.neohapsis.com, there's a CNAME resource record owned by sol.lab.neohapsis.com. That resource record contains sol's canonical (ultimate) name, which is jm.lab.neohapsis.com.

An authoritative name server typically knows all the information necessary to delegate requests to children zones. It conveys this information to other systems, even though it isn't technically authoritative for that information. For example, the name server responsible for the neohapsis.com. zone has NS records for lab.neohapsis.com. They should be identical to the authoritative NS records that the lab.neohapsis.com name server has for its top domain.

The NS record points to a domain name, such as sol.lab.neohapsis.com., and the neohapsis.com. zone's server needs to provide a glue resource record that tells a client the IP address for the NS record. So the neohapsis.com. zone's server sends these additional resource records:

lab.neohapsis.com.       NS    sol.lab.neohapsis.com. sol.lab.neohapsis.com.   A     7.6.5.23


Basic Use Case

Most operating systems have a simple stub resolver that relies on an external recursive name server. The resolver library translates user requests into a DNS query packet that's sent to the preconfigured local recursive name server. This friendly name server attempts to answer the question by referring to its authoritative data and cache and by querying other name servers for information. This process usually takes a series of requests. Figure 16-17 shows how a typical DNS request is handled.

Figure 16-17. DNS request traffic


The resolver creates an A query for the domain name www.google.com. and sends the query to its local recursive name server. First, the name server looks at its zones for anything in the domain name that it can answer for authoritatively, but it can't help with this query.

Then it looks in its cache for any useful information; for the sake of discussion, assume it comes up empty. The name server is preloaded with a list of root name servers, and it starts sending iterative queries to them. It asks several root name servers for the A record for www.google.com and eventually gets a response.

The response doesn't have the answer, however. Instead, it has multiple authority NS resource records that give the domain names for all com. name servers. The response also contains additional A resource records that give the numeric IP addresses for each specified name server.

The name server asks a com. name server for the A record for www.google.com. The response still doesn't have an answer, but this time, the authority section has four NS records for google.com. The additional section has four corresponding A records for the numeric IP addresses of these name servers.

Next, the name server asks a google.com. name server for the A record for www.google.com. In the real world, you learn that www.google.com. is an alias because you get an authoritative answer telling you that it's a CNAME for www.l.google.com. However, for this use case, pretend it returns an A record instead. The name server finally gets its A record for www.google.com., and the IP address is 1.2.3.4.

The name server then constructs an answer for the resolver code and sends it as a response to the initial recursive query. The resolver code extracts the IP address from the A record and hands it to the user application.

DNS Protocol Structure Primer

DNS is a binary protocol, so you know that integer issues are going to be involved. A DNS packet is essentially composed of a header followed by four variable-length fields: a questions section, an answer section, an authority section, and an additional section. This basic packet layout is shown in Figure 16-18.

Figure 16-18. DNS packet structure


The header provides information about how the packet should be interpreted. Figure 16-19 shows how it's structured.

Figure 16-19. DNS header structure


The DNS header contains a number of status bit fields and a series of record counts, indicating the number of resource records in the packet. These fields are described in the following list:

  • Identification (16 bits) This field is used to uniquely identify a query. Responses to a query must have the same ID or they are ignored.

  • QR (1 bit) This field indicates whether this packet contains a query (0) or response (1).

  • Opcode (4 bits) This field indicates what type of query is in the message. It's usually 0, meaning a standard query.

  • AA (1 bit) This field indicates whether the packet contains an authoritative answer.

  • TC (1 bit) This field indicates whether the answer is truncated because of size constraints.

  • RD (1 bit) This fieldrecursion desiredsets a query to indicate that the name server should recursively handle the query if possible.

  • RA (1 bit) This field is set by a name server to indicate whether recursion is available.

  • Rcode (4 bits) This field is used to indicate an error code (return code).

  • Questions count (16 bits) This field specifies the number of questions in the questions section; usually one.

  • Answer count (16 bits) This field specifies the total number of resource records in the answer section.

  • Authority count (16 bits) This field specifies the total number of NS resource records in the authority section.

  • Additional count (16 bits) This field specifies the total number of resource records returned in the additional section.

The questions section contains a series of question records, and the other sections contain resource records (RRs). The format of a question is shown in Figure 16-20.

Figure 16-20. DNS question structure


The fields for a question entry in a query are as follows:

  • Query name (variable) The domain name that's the subject of the query

  • Type (16 bits) A code indicating the type of resource records the client wants to retrieve

  • Class (16 bits) The class of resource record (almost always IN)

The format of a resource record structure is shown in Figure 16-21. The following list describes the fields for an RR:

  • Owner name (variable) The domain name to which this resource record belongs

  • Type (16 bits) The type of resource record

  • Class (16 bits) The class of resource record (almost always IN)

  • Time to live (32 bits) The time in seconds this RR can be cached before it should be discarded

  • RDATA length (unsigned 16-bit int) Length of the following RDATA field in bytes

  • RDATA (variable) Variable data in a format that depends on the specified type

Figure 16-21. DNS resource record data structure


DNS Names

Names are communicated in many places in DNS packets. These domain names aren't transmitted in a pure text format. Instead, they are transmitted as a series of labels. Each label contains a single-byte length value followed by the data bytes that make up this part of the name. Going back to the previous example of www.google.com, the name would look like Figure 16-22 in the packet.

Figure 16-22. DNS names


Each label length byte is followed by the data bytes that make up each domain label. The name ends at the root of the tree, which has an empty label with a length byte of zero.

A simple compression scheme using pointers can be used in domain names. If the top two bits are set in a label length byte, the remaining bits of the byte are combined with the next 8 bits from the packet (the next byte). They are used as an offset inside the DNS packet the pointer appears in, beginning at the start of the DNS header. This offset points to domain name information for the rest of the domain name. Using this simple scheme, multiple resource records using the same owner name (or sharing a common suffix) can write the shared name in the packet just one time. They can then refer to this shared name for all other subsequent resource records that refer to the same name.

Although this naming scheme is simple and can save valuable space in some places, it certainly complicates the DNS name-decoding scheme. Take a look at a simple (buggy) implementation of name parsing, and the following sections discuss potential problems with it.

int parse_dns_name(char *msg, char *name, int namelen,                    char *dest0, int destlen) {     int label_length, offset, bytes_read = 0;     char *ptr, *dest = dest0;     for(ptr = name; *ptr; ){         label_length = *ptr++;         /* check for pointers */             if((label_length & 0xC0) == 0xC0){                 offset = ((label_length & 0x3F) << 8) | *ptr;                 ptr = msg + offset;                 continue;             }         if(bytes_read + label_length > destlen)             return 1;         memcpy(dest, ptr, label_length);         ptr += label_length;         dest += label_length;         bytes_read += label_length;         *dest++ = '.';     }     if(dest != dest0)         dest--;     *dest = '\0';     return 0; }


This simple implementation of the specification has numerous problems, explained in the following sections, that demonstrate what can go wrong when parsing DNS names.

Failure to Deal with Invalid Label Lengths

The maximum size for a label is 63 bytes because setting the top 2 bits indicates that the byte is the first in a two-byte pointer, leaving 6 bits to represent a label length. That means any label in which one of the top bits is set but the other one isn't is an invalid length value. The preceding code doesn't adequately deal with this situation, resulting in larger domain labels than the specification allows. In this implementation, this problem carries additional consequences. Consider this line:

label_length = *ptr++;


Because ptr is signed, you know from Chapter 6 that this assignment sign-extends the value, so label_length can have a negative value. Later a size check is carried out:

if(bytes_read + label_length > destlen)     return 1;


Can you see why this check isn't adequate? In this check, label_length is a negative value, so bytes_read + label_length can be made a negative value. Hence, this length check doesn't catch the problem, and subsequently a large negative memcpy() occurs.

Insufficient Destination Length Checks

It's easy to overlook the space required for bytes that are appended manually when performing length checks. In the sample code, a period (.) is appended manually after each label. These periods simply aren't checked for in the length check; only label_length bytes are accounted for. In addition, the trailing NUL byte isn't accounted for in much the same way.

Insufficient Source Length Checks

Just as pointers aren't correctly verified to be in the packet, the code has no verification that source bytes being read are within the packet boundaries. If no NUL byte exists in the name section, this code keeps processing data until it runs past the end of the packetagain resulting in a potential information leak or denial of service. Even when the code does check that source bytes are within bounds, it omits this check when reading the second byte of a pointer or the amount of bytes specified in the label length.

Pointer Values Not Verified In Packet

When pointers are found, the ptr variable is set to point to the new location to continue reading the domain name. In this sample code, the new pointer is simply set to msg (the beginning of the DNS message) plus the supplied offset. The code never verifies that this new location is actually inside the packet, so it begins reading random memory from the program. This error might result in an information leak or a denial of serviceat any rate, it's not desirable behavior!

Special Pointer Values

When pointer compression methods are used, you can find a few more oddities. For example, a malicious user might create a loop. Say a pointer is 20 bytes into a DNS message and points to offset 20. If the sample code shown previously processes this pointer, it gets stuck in an infinite loop. This loop would probably end up causing a denial of service by not dealing with other DNS requests (especially if several resolutions were taking place in parallel with corrupt DNS pointers, such as this example).

Also, be aware that the code has no real verification that pointers are actually pointing to name data in a DNS message. They might be pointing to a TTL field, a length field, or a pointer byte (such as having a pointer at offset 20 that points to offset 21 in the packet). Generally, this oversight doesn't cause too many security problems, but it might serve as part of an evasion technique to bypass IDSs.

Length Variables

There are no 32-bit integers to specify data lengths in the DNS protocol; everything is 8 or 16 bits. Therefore, this section focuses on the issues with 16-bit length fields discussed at the beginning of the chapter.

The first issue is sign extensions of 16-bit values. You probably won't see this problem often, although when you do, it's likely a bug is present. Here's a simple example:

struct rrecord {     char *name;     int ttl;     short length, type, class;     char *data; } #define ROUNDUP(x) ((x + 7) & 0xFFFFFFF8) void *mymalloc(size_t length) {     return malloc(ROUNDUP(length)); } int parse_rrecord(char *data, int length, struct rrecord *rr) {     if(length < 2 + 2 + 2 + 4)         return 1;     rr->name = parse_name(data, &data);     if(!rr->name)         return 1;     rr->type = get_short(data);     data += 2;     rr->class = get_short(data);     data += 2;     rr->ttl = get_long(data);     data += 4;     rr->length = get_short(data);     data += 2;     length -= (4 + 2 + 2 + 2);     if(rr->length > length)         return 1;     rr->data = (char *)mymalloc(rr->length);     if(!rr->data)         return 1;     memcpy(rr->data, data, rr->length);     ... }


This code shows a typical malloc() implementation that's potentially vulnerable to an integer overflow. Because you're dealing with a protocol containing 16-bit length fields, allocation functions such as malloc() normally aren't dangerous because you can supply only 16-bit lengths, which aren't big enough to cause an integer wrap on a 32-bit integer size parameter. However, in this code, the 16-bit length value is sign-extended, so if the top bit is set, the high 16 bits of the value passed to mymalloc() are also set, allowing users to specify a size big enough to cause an integer wrap.

Note

This code wouldn't be vulnerable if the length parameter to parse_rrecord() was unsigned because the comparison of rr->length against length would cause rr->length to be sign-extended and then converted to unsigned, which is no doubt larger than length.


In addition to sign-extension issues, there are other complications when the program decides to make extensive use of 16-bit variables for sizes or holding length values. Specifically, if 16-bit values are used carelessly, the risk of integer overflows is present (in the same way programs dealing with protocols that have 32-bit lengths are vulnerable to integer overflows). In the context of DNS, any addition or multiplication on a 16-bit variable presents a potential danger if users can specify large 16-bit values. To understand this problem, take a look at a bug that was in Microsoft's DNS-parsing code. To understand the bug, you must first examine the allocation routine used to allocate records. The following code shows the Dns_AllocateRecord() function:

.text:76F239EC ; __stdcall Dns_AllocateRecord(x) .text:76F239EC _Dns_AllocateRecord@4 proc near .text:76F239EC .text:76F239EC .text:76F239EC arg_4           = word ptr 8 .text:76F239EC .text:76F239EC                 mov    edi, edi .text:76F239EE                 push   ebp .text:76F239EF                 mov    ebp, esp .text:76F239F1                 push   esi .text:76F239F2                 mov    si, [ebp+arg_4] .text:76F239F6                 movzx  eax, si .text:76F239F9                 add    eax, 18h .text:76F239FC                 push   eax .text:76F239FD                 call   _Dns_AllocZero@4 ; Dns_AllocZero(x) .text:76F23A02                 mov    edx, eax .text:76F23A04                 test   edx, edx .text:76F23A06                 jz     loc_76F2DCB5 .text:76F23A0C                 push   edi .text:76F23A0D                 push   6 .text:76F23A0F                 pop    ecx .text:76F23A10                 xor    eax, eax .text:76F23A12                 mov    edi, edx .text:76F23A14                 rep stosd .text:76F23A16                 mov    [edx+0Ah], si .text:76F23A1A                 mov    eax, edx .text:76F23A1C                 pop    edi .text:76F23A1D .text:76F23A1D loc_76F23A1D:                         ; CODE XREF: .text:76F2DCBF .text:76F23A1D                 pop    esi .text:76F23A1E                 pop    ebp .text:76F23A1F                 retn   4 .text:76F23A1F_Dns_AllocateRecord@4 endp


This assembly code roughly translates to the following C code:

/* sizeof DnsRecord structure is 24 (0x18) bytes */ struct DnsRecord {     unsigned short size;       /* offset 0x0A */     unsigned char data[0];       /* offset 0x18 */ } struct DnsRecord *Dns_AllocateRecord(unsigned short size) {     struct DnsRecord *record;     record = (struct DnsRecord *)Dns_AllocZero(size + sizeof(struct DnsRecord));     if(record == NULL){         SetLastError(8);         return NULL;     }     memset((void *)record, 0, sizeof(struct DnsRecord));     record->size = size;     return record; }


You might be wondering why a SetLastError() function is in the C code but not in the assembly. The assembly output shows that the code tests the return value of Dns_AllocZero() and then jumps if it returns zero (which happens at location 76F23A06). The code it jumps to isn't shown, but it calls SetLastError(). Interested readers can refer to this function in dnsapi.dll on Windows XP or dnsrslvr.dll on Windows 2000.

As you can see, this allocation routine could be dangerous. It takes a 16-bit size parameter, so if this function can ever be called with an allocation size of more than 65,535 bytes (the maximum representable 16-bit value), the high 16-bits are ignored, and a small data block not large enough to hold all the data will be allocated. It turns out that DNS packets are limited elsewhere in the code to a maximum of 16,384 bytes for TCP and 1,472 bytes for UDP, so you can't specify a big enough record to trigger an overflow under normal circumstances. However, take a closer look at how text records are processed. The following code is translated into C from the TxTRecordRead() function, which is used to parse records containing text fields. These records are composed of multiple text fields, each one consisting of a single-byte length field followed by text data.

struct DnsRecord *TxtRecordRead(int to_unicode,             unsigned char *src, unsigned char *end) {     unsigned short length;     int count, bytes_needed;     struct DnsRecord *record;     for(count = 0, bytes_needed = 0; src < end; count++){         length = *src++;         bytes_needed += ((to_unicode) ?                          2*length + 2 : length + 1);         src += length;     }     if(src != end){         SetLastError(0x0D);         return NULL;     }     record = Dns_AllocateRecord(                 ((count + 1) * sizeof(char *)) + bytes_needed);     ... copy data and pointers ... }


For every text field in the record, four bytes are allocated (for a pointer value to point to the text field), and two bytes are allocated for every byte appearing in the text data. The reason is that the data is converted in the text field from UTF-8 encoding to Unicode wide characters. Also, the code adds two bytes for the trailing NUL to appear after the text string it copies. When you have a zero-length record, it consists of a single byte: the length field, which has the value 0. For every zero-length record encountered, six bytes are added to the allocation size passed to Dns_AllocateRecord(): four bytes for the pointer, and two bytes for a NUL value. Six bytes for every one byte appearing in the record allows reaching the 16-bit boundary of 65,535 bytes with a record of around 10,922 bytes, which can be supplied in a TCP packet. Therefore, a buffer overflow can be triggered.

DNS Spoofing

DNS is a protocol for retrieving information from a large-scale distributed database, and it's used by clients of the service and servers that maintain the entire database. Because the system requires a large degree of trust, what can happen if attackers abuse this trust to feed bad information to those who request DNS information? The implications of this attack can be quite severe, depending on how clients use the false information. In the past, hostnames were commonly used for verification of a user's identity. For example, the UNIX rlogin service consulted a file with combinations of usernames and hostnames to authenticate incoming connections, instead of the username/password authentication most other services used at the time. Therefore, if attackers could forge DNS responses to make their IP addresses appear to be one of the hosts in this file, they could bypass authentication and log in to the target machine. These days, DNS names are rarely used to authenticate parties in such a direct manner; however, being able to forge DNS responses is still a serious issue.

The most serious current risk is impersonation of a legitimate site. Malicious nodes can pose as legitimate destinations and collect authentication details or other sensitive data. For example, attackers could pose as a retailer that clients usually visit (such as www.amazon.com/). By posing as the legitimate site and fooling certain clients, the malicious users might be able to collect Amazon login credentials and credit card information from clients browsing the site. These attackers would have to pull a few tricks to make the spoofed site seem authentic, but they can usually fool most users.

Cache Poisoning

The original resolver algorithm specified in DNS RFCs was vulnerable to a poisoning attack that enabled attackers to provide malicious IP addresses for arbitrary domain names. Assume that attackers have control of the zone at badperson.com. A victim asks the attackers' name server for the A records of www.badperson.com. They can respond by delegating authority for the www subdomain to the hostname they want to poison. For example, they could include an authority section in the response with these NS resource records:

www.badperson.com. NS ns1.google.com. www.badperson.com. NS ns2.google.com. www.badperson.com. NS ns3.google.com. www.badperson.com. NS ns4.google.com.


Basically, the attackers are telling the victim that the subdomain www.badperson.com is handled by four authoritative name servers, which happen to be Google's name servers. The death blow comes in the additional section in the response, where attackers place the A resource records for the Google name servers:

ns1.google.com A 10.20.30.40 ns2.google.com A 10.20.30.40 ns3.google.com A 10.20.30.40 ns4.google.com A 10.20.30.40


RFC 1034 says the resolution code should check that the delegation is to a "better" name server then the one used in the current query. In this example, the query for www.badperson.com. was made to the badperson.com. name server. This request is delegated to the Google name servers, but the packet is saying is the name servers are authoritative for the www.badperson.com. subdomain. This is good enough to pass the algorithm's "better" check. The real problem is the algorithm suggests that code blindly honor the supplemental A records that purport to be helpful glue. Vulnerable implementations of BIND circa 1997 would enter these A records into the cache. Any future requests by victims for a google.com. host would end up contacting the attackers' evil name server at 10.20.30.40.

Windows Resolver Bug

Windows resolvers also have a bug that allows attackers to hijack popular Web sites for specific targets. Say attackers have control of the zone at badperson.com. A victim asks their name server for the A records of www.badperson.com. This time, attackers can respond by delegating the authority for the com. domain to an evil name server under their control. The authority section might contain this NS resource record:

com. NS evil.reallybad.org.


There's no reason the victim's resolver should honor this response, as it's completely illogical. However, Windows cached this NS record because of an implementation bug. This means that later, when the resolver needs to contact a name server for the .com zone, it contacts evil.reallybad.org instead. Windows NT and Windows 2000 SP1 and SP2 were vulnerable by default to this problem, and it also affected various Symantec products.

Spoofing Responses

Most communications between DNS clients and servers occur over UDP, an unreliable and unauthenticated transport. ("Unauthenticated" means there's no way to verify that sender are who they say they are.) TCP is also an unauthenticated transport but to a much lesser extent. (For more information, refer to Chapter 14.) Therefore, how does a client or server know a request is from a legitimate source? The answer is simple: They don't, in a lot of circumstances! The traditional way of validating DNS responses is using the DNS ID field in the header. When a DNS client generates a question, it assigns an (ostensibly) random number for the ID field. When it receives responses, it checks that the DNS ID field matches the request. This check is done by verifying that the response packet has the same value in the DNS ID field as the query packet the client originally sent. With this information, a couple of attacks could be launched. One of the most obvious is a man-in-the-middle attack by someone in a position to observe DNS traffic. This attack is fairly easy to achieve, so chalk it up as a known risk and focus your attention on blind spoofing.

DNS spoofing issues affect both DNS server and client implementations because servers make requests on behalf of clients and usually cache results (if they are configured for recursion). When a server issues a DNS request to recursively resolve a remote host on behalf of a client, remote responses to servers could be forged and subsequently cached. Basically, most attacks of this nature revolve around how predictable an implementation's DNS ID generation algorithm is. The simplest implementations have fixed increments (usually of 1) for each question they generate. In the past, BIND (one of the premier name servers on the Internet) was vulnerable to this problem, as pointed out by Secure Networks Inc. and CORE (documented at http://attrition.org/security/advisory/nai/SNI-12.BIND.advisory). The advisory walks through the steps required to cache poison name servers by forging responses from a remote DNS server.

Note

In some ways, this attack is not unlike the TCP sequence number spoofing mentioned in Chapter 14, except DNS IDs need to be exact. Injecting TCP data just requires a sequence number within the TCP window.


Dan Bernstein gives a great summary of the current risks of blind forgery at http://cr.yp.to/djbdns/forgery.html:

An attacker from anywhere on the Internet, without access to the client network and without access to the server network, can also forge responses, although not so easily. In particular, he has to guess the query time, the DNS ID (16 bits), and the DNS query port (15-16 bits). The dnscache program uses a cryptographic generator for the ID and query port to make them extremely difficult to predict. However,

  • an attacker who makes a few billion random guesses is likely to succeed at least once;

  • tens of millions of guesses are adequate with a colliding attack;

  • against BIND, a hundred thousand guesses are adequate, because BIND keeps using the same port for every query; and

  • against old versions of BIND, a thousand guesses are adequate with a colliding attack.

The lack of authentication in this protocol is a recognized problem, and steps have been taken to help secure it. Specifically, DNS messages can be cryptographically verified by using the TKEY and TSIG record types, but this method isn't yet used extensively (even though most implementations support it). For this reason, you can't assume that cryptographic verification protects an implementation from DNS ID prediction vulnerabilities unless the implementation you're reviewing mandates the use of the DNS cryptographic features. DNS ID generation algorithms based on known values also might not be very secure. For example, a DNS ID based on the time returned from the time() functions might be quite easy to guess.




The Art of Software Security Assessment. Identifying and Preventing Software Vulnerabilities
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
ISBN: 0321444426
EAN: 2147483647
Year: 2004
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net