xorl %eax, %eax

CVE-2009-3602: Unbound DNSSEC Records Cache Poisoning

leave a comment »

Unbound is a validating, recursive, and caching open source DNS server. Releases prior to 1.3.4 contain a design flaw in the handling of DNSSEC NSEC3 records.
The bug was discovered and disclosed by the Unbound development team and here is the vulnerable routine as seen in validator/val_nsec3.c of 1.3.3 release of the DNS server.

enum sec_status
nsec3_prove_nods(struct module_env* env, struct val_env* ve,
        struct ub_packed_rrset_key** list, size_t num,
        struct query_info* qinfo, struct key_entry_key* kkey)
{
        rbtree_t ct;
        struct nsec3_filter flt;
        struct ce_response ce;
        struct ub_packed_rrset_key* rrset;
        int rr;
        log_assert(qinfo->qtype == LDNS_RR_TYPE_DS);

        if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
                return sec_status_bogus; /* no valid NSEC3s, bogus */
        rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
        filter_init(&flt, list, num, qinfo); /* init RR iterator */
        if(!flt.zone)
                return sec_status_bogus; /* no RRs */
        if(nsec3_iteration_count_high(ve, &flt, kkey))
                return sec_status_insecure; /* iteration count too high */

        /* Look for a matching NSEC3 to qname -- this is the normal
         * NODATA case. */
      ...
}

As you can see, before proceeding it checks if the NSEC3 is valid. To ensure this, it checks that:
– passed list is not NULL
– counter ‘num’ is not set to 0
– there is a key in the key entry pointer ‘kkey’
– the passed key entry pointer is good, meaning that this entry doesn’t do any locking
Clearly, there is no check of the NSEC3 signature that should be valid in order to continue with the DNS record. This means that a malicious user could poison the cache of Unbound regardless of the validity of its signature since there is no verification code present.
This was fixed by adding a new routine named list_is_secure() which performs verification like this:

/** test if list is all secure */
static int
list_is_secure(struct module_env* env, struct val_env* ve,
	struct ub_packed_rrset_key** list, size_t num,
	struct key_entry_key* kkey)
{
	size_t i;
	enum sec_status sec;
	for(i=0; i<num; i++) {
		if(list[i]->rk.type != htons(LDNS_RR_TYPE_NSEC3))
			continue;
		sec = val_verify_rrset_entry(env, ve, list[i], kkey);
		if(sec != sec_status_secure) {
			verbose(VERB_ALGO, "NSEC3 did not verify");
			return 0;
		}
	}
	return 1;
}

It iterates through the entire list and checks each entry against LDNS_RR_TYPE_NSEC3 and it also utilizes val_verify_rrset_entry() to verify the equivalent DNSKEY RRset signature. This new function was inserted in nsec3_prove_nods() after the basic NSEC3 if check.

 	if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
 		return sec_status_bogus; /* no valid NSEC3s, bogus */
+	if(!list_is_secure(env, ve, list, num, kkey))
+		return sec_status_bogus; /* not all NSEC3 records secure */
 	rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */

Written by xorl

October 21, 2009 at 20:09

Posted in bugs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s