2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 14:07:59 +00:00

2604. [func] Add support for DNS rebinding attack prevention through

new options, deny-answer-addresses and
			deny-answer-aliases.  Based on contributed code from
			JD Nurmi, Google. [RT #18192]
This commit is contained in:
Tatuya JINMEI 神明達哉 2009-05-29 22:22:37 +00:00
parent fc7ecc628d
commit 40d0f115a6
14 changed files with 641 additions and 53 deletions

View File

@ -1,3 +1,8 @@
2604. [func] Add support for DNS rebinding attack prevention through
new options, deny-answer-addresses and
deny-answer-aliases. Based on contributed code from
JD Nurmi, Google. [RT #18192]
2603. [port] win32: handle .exe extension of named-checkzone and
named-comilezone argv[0] names under windows.
[RT #19767]

2
README
View File

@ -54,6 +54,8 @@ BIND 9.7.0
internal information about query failures, especially about
server failures.
Add support for DNS rebinding attack prevention.
BIND 9.6.0
BIND 9.6.0 includes a number of changes from BIND 9.5 and earlier

View File

@ -1,25 +1,7 @@
/*
* Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: bind.keys.h,v 1.3 2009/03/05 23:47:35 tbox Exp $ */
#define TRUSTED_KEYS "\
trusted-keys {\n\
# NOTE: This key expires September 2009 \n\
# Go to https://www.isc.org/solutions/dlv to download a replacement\n\
# NOTE: This key expires September 2009 \n\
# Go to https://www.isc.org/solutions/dlv to download a replacement\n\
dlv.isc.org. 257 3 5 \"BEAAAAPHMu/5onzrEE7z1egmhg/WPO0+juoZrW3euWEn4MxDCE1+lLy2 brhQv5rN32RKtMzX6Mj70jdzeND4XknW58dnJNPCxn8+jAGl2FZLK8t+ 1uq4W+nnA3qO2+DL+k6BD4mewMLbIYFwe0PG73Te9fZ2kJb56dhgMde5 ymX4BI/oQ+cAK50/xvJv00Frf8kw6ucMTwFlgPe+jnGxPPEmHAte/URk Y62ZfkLoBAADLHQ9IrS2tryAe7mbBZVcOwIeU/Rw/mRx/vwwMCTgNboM QKtUdvNXDrYJDSHZws3xiRXF1Rf+al9UmZfSav/4NWLKjHzpT59k/VSt TDN0YUuWrBNh\";\n\
};\n\
"

View File

@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: server.c,v 1.530 2009/03/04 23:48:01 tbox Exp $ */
/* $Id: server.c,v 1.531 2009/05/29 22:22:35 jinmei Exp $ */
/*! \file */
@ -278,8 +278,8 @@ end_reserved_dispatches(ns_server_t *server, isc_boolean_t all);
*/
static isc_result_t
configure_view_acl(const cfg_obj_t *vconfig, const cfg_obj_t *config,
const char *aclname, cfg_aclconfctx_t *actx,
isc_mem_t *mctx, dns_acl_t **aclp)
const char *aclname, const char *acltuplename,
cfg_aclconfctx_t *actx, isc_mem_t *mctx, dns_acl_t **aclp)
{
isc_result_t result;
const cfg_obj_t *maps[3];
@ -305,13 +305,21 @@ configure_view_acl(const cfg_obj_t *vconfig, const cfg_obj_t *config,
*/
return (ISC_R_SUCCESS);
if (acltuplename != NULL) {
/*
* If the ACL is given in an optional tuple, retrieve it.
* The parser should have ensured that a valid object be
* returned.
*/
aclobj = cfg_tuple_get(aclobj, acltuplename);
}
result = cfg_acl_fromconfig(aclobj, config, ns_g_lctx,
actx, mctx, 0, aclp);
return (result);
}
/*%
* Configure a sortlist at '*aclp'. Essentially the same as
* configure_view_acl() except it calls cfg_acl_fromconfig with a
@ -355,6 +363,80 @@ configure_view_sortlist(const cfg_obj_t *vconfig, const cfg_obj_t *config,
return (result);
}
static isc_result_t
configure_view_nametable(const cfg_obj_t *vconfig, const cfg_obj_t *config,
const char *confname, const char *conftuplename,
isc_mem_t *mctx, dns_rbt_t **rbtp)
{
isc_result_t result;
const cfg_obj_t *maps[3];
const cfg_obj_t *obj = NULL;
const cfg_listelt_t *element;
int i = 0;
dns_fixedname_t fixed;
dns_name_t *name;
isc_buffer_t b;
const char *str;
const cfg_obj_t *nameobj;
if (*rbtp != NULL)
dns_rbt_destroy(rbtp);
if (vconfig != NULL)
maps[i++] = cfg_tuple_get(vconfig, "options");
if (config != NULL) {
const cfg_obj_t *options = NULL;
(void)cfg_map_get(config, "options", &options);
if (options != NULL)
maps[i++] = options;
}
maps[i] = NULL;
(void)ns_config_get(maps, confname, &obj);
if (obj == NULL)
/*
* No value available. *rbtp == NULL.
*/
return (ISC_R_SUCCESS);
if (conftuplename != NULL) {
obj = cfg_tuple_get(obj, conftuplename);
if (cfg_obj_isvoid(obj))
return (ISC_R_SUCCESS);
}
result = dns_rbt_create(mctx, NULL, NULL, rbtp);
if (result != ISC_R_SUCCESS)
return (result);
dns_fixedname_init(&fixed);
name = dns_fixedname_name(&fixed);
for (element = cfg_list_first(obj);
element != NULL;
element = cfg_list_next(element)) {
nameobj = cfg_listelt_value(element);
str = cfg_obj_asstring(nameobj);
isc_buffer_init(&b, str, strlen(str));
isc_buffer_add(&b, strlen(str));
CHECK(dns_name_fromtext(name, &b, dns_rootname,
ISC_FALSE, NULL));
/*
* We don't need the node data, but need to set dummy data to
* avoid a partial match with an empty node. For example, if
* we have foo.example.com and bar.example.com, we'd get a match
* for baz.example.com, which is not the expected result.
* We simply use (void *)1 as the dummy data.
*/
CHECK(dns_rbt_addname(*rbtp, name, (void *)1));
}
return (result);
cleanup:
dns_rbt_destroy(rbtp);
return (result);
}
static isc_result_t
configure_view_dnsseckey(const cfg_obj_t *vconfig, const cfg_obj_t *key,
dns_keytable_t *keytable, isc_mem_t *mctx)
@ -1722,10 +1804,10 @@ configure_view(dns_view_t *view, const cfg_obj_t *config,
/*
* Configure the "match-clients" and "match-destinations" ACL.
*/
CHECK(configure_view_acl(vconfig, config, "match-clients", actx,
CHECK(configure_view_acl(vconfig, config, "match-clients", NULL, actx,
ns_g_mctx, &view->matchclients));
CHECK(configure_view_acl(vconfig, config, "match-destinations", actx,
ns_g_mctx, &view->matchdestinations));
CHECK(configure_view_acl(vconfig, config, "match-destinations", NULL,
actx, ns_g_mctx, &view->matchdestinations));
/*
* Configure the "match-recursive-only" option.
@ -1797,20 +1879,20 @@ configure_view(dns_view_t *view, const cfg_obj_t *config,
* "allow-recursion", and "allow-recursion-on" acls if
* configured in named.conf.
*/
CHECK(configure_view_acl(vconfig, config, "allow-query-cache",
CHECK(configure_view_acl(vconfig, config, "allow-query-cache", NULL,
actx, ns_g_mctx, &view->queryacl));
CHECK(configure_view_acl(vconfig, config, "allow-query-cache-on",
CHECK(configure_view_acl(vconfig, config, "allow-query-cache-on", NULL,
actx, ns_g_mctx, &view->queryonacl));
if (view->queryonacl == NULL)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-query-cache-on", actx,
"allow-query-cache-on", NULL, actx,
ns_g_mctx, &view->queryonacl));
if (strcmp(view->name, "_bind") != 0) {
CHECK(configure_view_acl(vconfig, config, "allow-recursion",
actx, ns_g_mctx,
NULL, actx, ns_g_mctx,
&view->recursionacl));
CHECK(configure_view_acl(vconfig, config, "allow-recursion-on",
actx, ns_g_mctx,
NULL, actx, ns_g_mctx,
&view->recursiononacl));
}
@ -1823,7 +1905,7 @@ configure_view(dns_view_t *view, const cfg_obj_t *config,
if (view->queryacl == NULL && view->recursionacl != NULL)
dns_acl_attach(view->recursionacl, &view->queryacl);
if (view->queryacl == NULL && view->recursion)
CHECK(configure_view_acl(vconfig, config, "allow-query",
CHECK(configure_view_acl(vconfig, config, "allow-query", NULL,
actx, ns_g_mctx, &view->queryacl));
if (view->recursion &&
view->recursionacl == NULL && view->queryacl != NULL)
@ -1835,19 +1917,20 @@ configure_view(dns_view_t *view, const cfg_obj_t *config,
*/
if (view->recursionacl == NULL && view->recursion)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-recursion",
"allow-recursion", NULL,
actx, ns_g_mctx,
&view->recursionacl));
if (view->recursiononacl == NULL && view->recursion)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-recursion-on",
"allow-recursion-on", NULL,
actx, ns_g_mctx,
&view->recursiononacl));
if (view->queryacl == NULL) {
if (view->recursion)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-query-cache", actx,
ns_g_mctx, &view->queryacl));
"allow-query-cache", NULL,
actx, ns_g_mctx,
&view->queryacl));
else {
if (view->queryacl != NULL)
dns_acl_detach(&view->queryacl);
@ -1855,6 +1938,25 @@ configure_view(dns_view_t *view, const cfg_obj_t *config,
}
}
/*
* Filter setting on addresses in the answer section.
*/
CHECK(configure_view_acl(vconfig, config, "deny-answer-addresses",
"acl", actx, ns_g_mctx, &view->denyansweracl));
CHECK(configure_view_nametable(vconfig, config, "deny-answer-addresses",
"except-from", ns_g_mctx,
&view->answeracl_exclude));
/*
* Filter setting on names (CNAME/DNAME targets) in the answer section.
*/
CHECK(configure_view_nametable(vconfig, config, "deny-answer-aliases",
"name", ns_g_mctx,
&view->denyanswernames));
CHECK(configure_view_nametable(vconfig, config, "deny-answer-aliases",
"except-from", ns_g_mctx,
&view->answernames_exclude));
/*
* Configure sortlist, if set
*/
@ -1868,19 +1970,19 @@ configure_view(dns_view_t *view, const cfg_obj_t *config,
*/
if (view->notifyacl == NULL)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-notify", actx,
"allow-notify", NULL, actx,
ns_g_mctx, &view->notifyacl));
if (view->transferacl == NULL)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-transfer", actx,
"allow-transfer", NULL, actx,
ns_g_mctx, &view->transferacl));
if (view->updateacl == NULL)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-update", actx,
"allow-update", NULL, actx,
ns_g_mctx, &view->updateacl));
if (view->upfwdacl == NULL)
CHECK(configure_view_acl(NULL, ns_g_config,
"allow-update-forwarding", actx,
"allow-update-forwarding", NULL, actx,
ns_g_mctx, &view->upfwdacl));
obj = NULL;
@ -3301,7 +3403,7 @@ load_configuration(const char *filename, ns_server_t *server,
else
isc_quota_soft(&server->recursionquota, 0);
CHECK(configure_view_acl(NULL, config, "blackhole", &aclconfctx,
CHECK(configure_view_acl(NULL, config, "blackhole", NULL, &aclconfctx,
ns_g_mctx, &server->blackholeacl));
if (server->blackholeacl != NULL)
dns_dispatchmgr_setblackhole(ns_g_dispatchmgr,

View File

@ -15,7 +15,7 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# $Id: ans.pl,v 1.10 2007/09/24 04:13:25 marka Exp $
# $Id: ans.pl,v 1.11 2009/05/29 22:22:36 jinmei Exp $
#
# Ad hoc name server
@ -52,6 +52,7 @@ for (;;) {
my @questions = $packet->question;
my $qname = $questions[0]->qname;
my $qtype = $questions[0]->qtype;
if ($qname eq "cname1.example.com") {
# Data for the "cname + other data / 1" test
@ -61,6 +62,32 @@ for (;;) {
# Data for the "cname + other data / 2" test: same RRs in opposite order
$packet->push("answer", new Net::DNS::RR("cname2.example.com 300 A 1.2.3.4"));
$packet->push("answer", new Net::DNS::RR("cname2.example.com 300 CNAME cname2.example.com"));
} elsif ($qname eq "www.example.org" || $qname eq "www.example.net" ||
$qname eq "badcname.example.org" ||
$qname eq "goodcname.example.org" ||
$qname eq "foo.baddname.example.org" ||
$qname eq "foo.gooddname.example.org") {
# Data for address/alias filtering.
if ($qtype eq "A") {
$packet->push("answer",
new Net::DNS::RR($qname .
" 300 A 192.0.2.1"));
} elsif ($qtype eq "AAAA") {
$packet->push("answer",
new Net::DNS::RR($qname .
" 300 AAAA 2001:db8:beef::1"));
}
} elsif ($qname eq "badcname.example.net" ||
$qname eq "goodcname.example.net") {
# Data for CNAME/DNAME filtering. We need to make one-level
# delegation to avoid automatic acceptance for subdomain aliases
$packet->push("authority", new Net::DNS::RR("example.net 300 NS ns.example.net"));
$packet->push("additional", new Net::DNS::RR("ns.example.net 300 A 10.53.0.3"));
} elsif ($qname =~ /sub\.example\.org/) {
# Data for CNAME/DNAME filtering. The final answers are
# expected to be accepted regardless of the filter setting.
$packet->push("authority", new Net::DNS::RR("sub.example.org 300 NS ns.sub.example.org"));
$packet->push("additional", new Net::DNS::RR("ns.sub.example.org 300 A 10.53.0.3"));
} else {
# Data for the "bogus referrals" test
$packet->push("authority", new Net::DNS::RR("below.www.example.com 300 NS ns.below.www.example.com"));

View File

@ -15,7 +15,7 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# $Id: ans.pl,v 1.9 2007/09/24 04:13:25 marka Exp $
# $Id: ans.pl,v 1.10 2009/05/29 22:22:36 jinmei Exp $
#
# Ad hoc name server
@ -50,7 +50,42 @@ for (;;) {
$packet->header->qr(1);
$packet->push("answer", new Net::DNS::RR("www.example.com 300 A 1.2.3.4"));
my @questions = $packet->question;
my $qname = $questions[0]->qname;
if ($qname eq "badcname.example.net") {
$packet->push("answer",
new Net::DNS::RR($qname .
" 300 CNAME badcname.example.org"));
} elsif ($qname eq "foo.baddname.example.net") {
$packet->push("answer",
new Net::DNS::RR("baddname.example.net" .
" 300 DNAME baddname.example.org"));
} elsif ($qname eq "foo.gooddname.example.net") {
$packet->push("answer",
new Net::DNS::RR("gooddname.example.net" .
" 300 DNAME gooddname.example.org"));
} elsif ($qname eq "goodcname.example.net") {
$packet->push("answer",
new Net::DNS::RR($qname .
" 300 CNAME goodcname.example.org"));
} elsif ($qname eq "cname.sub.example.org") {
$packet->push("answer",
new Net::DNS::RR($qname .
" 300 CNAME ok.sub.example.org"));
} elsif ($qname eq "ok.sub.example.org") {
$packet->push("answer",
new Net::DNS::RR($qname . " 300 A 192.0.2.1"));
} elsif ($qname eq "www.dname.sub.example.org") {
$packet->push("answer",
new Net::DNS::RR("dname.sub.example.org" .
" 300 DNAME ok.sub.example.org"));
} elsif ($qname eq "www.ok.sub.example.org") {
$packet->push("answer",
new Net::DNS::RR($qname . " 300 A 192.0.2.1"));
} else {
$packet->push("answer", new Net::DNS::RR("www.example.com 300 A 1.2.3.4"));
}
$sock->send($packet->data);

View File

@ -14,9 +14,10 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# $Id: clean.sh,v 1.1 2008/07/17 01:15:34 marka Exp $
# $Id: clean.sh,v 1.2 2009/05/29 22:22:36 jinmei Exp $
#
# Clean up after resolver tests.
#
rm -f */named.memstats
rm -f dig.out

View File

@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: named.conf,v 1.13 2007/06/18 23:47:30 tbox Exp $ */
/* $Id: named.conf,v 1.14 2009/05/29 22:22:36 jinmei Exp $ */
controls { /* empty */ };
@ -29,6 +29,11 @@ options {
listen-on-v6 { none; };
recursion yes;
acache-enable yes;
deny-answer-addresses { 192.0.2.0/24; 2001:db8:beef::/48; }
except-from { "example.org"; };
deny-answer-aliases { "example.org"; }
except-from { "goodcname.example.net";
"gooddname.example.net"; };
};
zone "." {

View File

@ -15,7 +15,7 @@
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# $Id: tests.sh,v 1.9 2007/06/19 23:47:05 tbox Exp $
# $Id: tests.sh,v 1.10 2009/05/29 22:22:36 jinmei Exp $
SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
@ -35,5 +35,75 @@ $DIG +tcp cname2.example.com. a @10.53.0.1 -p 5300 >/dev/null || status=1
echo "I:check that server is still running"
$DIG +tcp www.example.com. a @10.53.0.1 -p 5300 >/dev/null || status=1
echo "I:checking answer IPv4 address filtering (deny)"
ret=0
$DIG +tcp www.example.net @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: SERVFAIL" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking answer IPv6 address filtering (deny)"
ret=0
$DIG +tcp www.example.net @10.53.0.1 aaaa -p 5300 > dig.out || ret=1
grep "status: SERVFAIL" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking answer IPv4 address filtering (accept)"
ret=0
$DIG +tcp www.example.org @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: NOERROR" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking answer IPv6 address filtering (accept)"
ret=0
$DIG +tcp www.example.org @10.53.0.1 aaaa -p 5300 > dig.out || ret=1
grep "status: NOERROR" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking CNAME target filtering (deny)"
ret=0
$DIG +tcp badcname.example.net @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: SERVFAIL" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking CNAME target filtering (accept)"
ret=0
$DIG +tcp goodcname.example.net @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: NOERROR" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking CNAME target filtering (accept due to subdomain)"
ret=0
$DIG +tcp cname.sub.example.org @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: NOERROR" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking DNAME target filtering (deny)"
ret=0
$DIG +tcp foo.baddname.example.net @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: SERVFAIL" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking DNAME target filtering (accept)"
ret=0
$DIG +tcp foo.gooddname.example.net @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: NOERROR" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:checking DNAME target filtering (accept due to subdomain)"
ret=0
$DIG +tcp www.dname.sub.example.org @10.53.0.1 a -p 5300 > dig.out || ret=1
grep "status: NOERROR" dig.out > /dev/null || ret=1
if [ $ret != 0 ]; then echo "I:failed"; fi
status=`expr $status + $ret`
echo "I:exit status: $status"
exit $status

View File

@ -18,7 +18,7 @@
- PERFORMANCE OF THIS SOFTWARE.
-->
<!-- File: $Id: Bv9ARM-book.xml,v 1.409 2009/05/14 20:46:04 jreed Exp $ -->
<!-- File: $Id: Bv9ARM-book.xml,v 1.410 2009/05/29 22:22:36 jinmei Exp $ -->
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<title>BIND 9 Administrator Reference Manual</title>
@ -2861,6 +2861,19 @@ $ORIGIN 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.
</para>
</entry>
</row>
<row rowsep="0">
<entry colname="1">
<para>
<varname>namelist</varname>
</para>
</entry>
<entry colname="2">
<para>
A list of one or more <varname>domain_name</varname>
elements.
</para>
</entry>
</row>
<row rowsep="0">
<entry colname="1">
<para>
@ -4951,6 +4964,8 @@ badresp:1,adberr:0,findfail:0,valfail:0]
<optional> disable-empty-zone <replaceable>zone_name</replaceable> ; </optional>
<optional> zero-no-soa-ttl <replaceable>yes_or_no</replaceable> ; </optional>
<optional> zero-no-soa-ttl-cache <replaceable>yes_or_no</replaceable> ; </optional>
<optional> deny-answer-addresses { <replaceable>address_match_list</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
<optional> deny-answer-aliases { <replaceable>namelist</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
};
</programlisting>
@ -8426,6 +8441,142 @@ XXX: end of RFC1918 addresses #defined out -->
</sect3>
<sect3>
<title>Content Filtering</title>
<para>
<acronym>BIND</acronym> 9 provides the ability to filter
out DNS responses from external DNS servers containing
certain types of data in the answer section.
Specifically, it can reject address (A or AAAA) records if
the corresponding IPv4 or IPv6 addresses match the given
<varname>address_match_list</varname> of the
<command>deny-answer-addresses</command> option.
It can also reject CNAME or DNAME records if the "alias"
name (i.e., the CNAME alias or the substituted query name
due to DNAME) matches the
given <varname>namelist</varname> of the
<command>deny-answer-aliases</command> option, where
"match" means the alias name is a subdomain of one of
the <varname>name_list</varname> elements.
If the optional <varname>namelist</varname> is specified
with <command>except-from</command>, records whose query name
matches the list will be accepted regardless of the filter
setting.
Likewise, if the alias name is a subdomain of the
corresponding zone, the <command>deny-answer-aliases</command>
filter will not apply;
for example, even if "example.com" is specified for
<command>deny-answer-aliases</command>,
</para>
<programlisting>www.example.com. CNAME xxx.example.com.</programlisting>
<para>
returned by an "example.com" server will be accepted.
</para>
<para>
In the <varname>address_match_list</varname> of the
<command>deny-answer-addresses</command> option, only
<varname>ip_addr</varname>
and <varname>ip_prefix</varname>
are meaningful;
any <varname>key_id</varname> will be silently ignored.
</para>
<para>
If a response message is rejected due to the filtering,
the entire message is discarded without being cached, and
a SERVFAIL error will be returned to the client.
</para>
<para>
This filtering is intended to prevent "DNS rebinding attacks," in
which an attacker, in response to a query for a domain name the
attacker controls, returns an IP address within your own network or
an alias name within your own domain.
A naive web browser or script could then serve as an
unintended proxy, allowing the attacker
to get access to an internal node of your local network
that couldn't be externally accessed otherwise.
See the paper available at
<ulink>
http://portal.acm.org/citation.cfm?id=1315245.1315298
</ulink>
for more details about the attacks.
</para>
<para>
For example, if you own a domain named "example.net" and
your internal network uses an IPv4 prefix 192.0.2.0/24,
you might specify the following rules:
</para>
<programlisting>deny-answer-addresses { 192.0.2.0/24; } except-from { "example.net"; };
deny-answer-aliases { "example.net"; };
</programlisting>
<para>
If an external attacker lets a web browser in your local
network look up an IPv4 address of "attacker.example.com",
the attacker's DNS server would return a response like this:
</para>
<programlisting>attacker.example.com. A 192.0.2.1</programlisting>
<para>
in the answer section.
Since the rdata of this record (the IPv4 address) matches
the specified prefix 192.0.2.0/24, this response will be
ignored.
</para>
<para>
On the other hand, if the browser looks up a legitimate
internal web server "www.example.net" and the
following response is returned to
the <acronym>BIND</acronym> 9 server
</para>
<programlisting>www.example.net. A 192.0.2.2</programlisting>
<para>
it will be accepted since the owner name "www.example.net"
matches the <command>except-from</command> element,
"example.net".
</para>
<para>
Note that this is not really an attack on the DNS per se.
In fact, there is nothing wrong for an "external" name to
be mapped to your "internal" IP address or domain name
from the DNS point of view.
It might actually be provided for a legitimate purpose,
such as for debugging.
As long as the mapping is provided by the correct owner,
it is not possible or does not make sense to detect
whether the intent of the mapping is legitimate or not
within the DNS.
The "rebinding" attack must primarily be protected at the
application that uses the DNS.
For a large site, however, it may be difficult to protect
all possible applications at once.
This filtering feature is provided only to help such an
operational environment;
it is generally discouraged to turn it on unless you are
very sure you have no other choice and the attack is a
real threat for your applications.
</para>
<para>
Care should be particularly taken if you want to use this
option for addresses within 127.0.0.0/8.
These addresses are obviously "internal", but many
applications conventionally rely on a DNS mapping from
some name to such an address.
Filtering out DNS records containing this address
spuriously can break such applications.
</para>
</sect3>
</sect2>
<sect2 id="server_statement_grammar">

View File

@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: view.h,v 1.116 2009/01/27 22:29:59 jinmei Exp $ */
/* $Id: view.h,v 1.117 2009/05/29 22:22:37 jinmei Exp $ */
#ifndef DNS_VIEW_H
#define DNS_VIEW_H 1
@ -128,6 +128,10 @@ struct dns_view {
dns_acl_t * transferacl;
dns_acl_t * updateacl;
dns_acl_t * upfwdacl;
dns_acl_t * denyansweracl;
dns_rbt_t * answeracl_exclude;
dns_rbt_t * denyanswernames;
dns_rbt_t * answernames_exclude;
isc_boolean_t requestixfr;
isc_boolean_t provideixfr;
isc_boolean_t requestnsid;

View File

@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: resolver.c,v 1.398 2009/05/11 02:38:35 tbox Exp $ */
/* $Id: resolver.c,v 1.399 2009/05/29 22:22:36 jinmei Exp $ */
/*! \file */
@ -4837,6 +4837,134 @@ dname_target(dns_rdataset_t *rdataset, dns_name_t *qname, dns_name_t *oname,
return (result);
}
static isc_boolean_t
is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
dns_rdataset_t *rdataset)
{
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
struct in_addr ina;
struct in6_addr in6a;
isc_netaddr_t netaddr;
char addrbuf[ISC_NETADDR_FORMATSIZE];
char namebuf[DNS_NAME_FORMATSIZE];
char classbuf[64];
char typebuf[64];
int match;
/* By default, we allow any addresses. */
if (view->denyansweracl == NULL)
return (ISC_TRUE);
/*
* If the owner name matches one in the exclusion list, either exactly
* or partially, allow it.
*/
if (view->answeracl_exclude != NULL) {
dns_rbtnode_t *node = NULL;
result = dns_rbt_findnode(view->answeracl_exclude, name, NULL,
&node, NULL, 0, NULL, NULL);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
return (ISC_TRUE);
}
/*
* Otherwise, search the filter list for a match for each address
* record. If a match is found, the address should be filtered,
* so should the entire answer.
*/
for (result = dns_rdataset_first(rdataset);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(rdataset)) {
dns_rdata_reset(&rdata);
dns_rdataset_current(rdataset, &rdata);
if (rdataset->type == dns_rdatatype_a) {
INSIST(rdata.length == sizeof(ina.s_addr));
memcpy(&ina.s_addr, rdata.data, sizeof(ina.s_addr));
isc_netaddr_fromin(&netaddr, &ina);
} else {
INSIST(rdata.length == sizeof(in6a.s6_addr));
memcpy(in6a.s6_addr, rdata.data, sizeof(in6a.s6_addr));
isc_netaddr_fromin6(&netaddr, &in6a);
}
result = dns_acl_match(&netaddr, NULL, view->denyansweracl,
&view->aclenv, &match, NULL);
if (result == ISC_R_SUCCESS && match > 0) {
isc_netaddr_format(&netaddr, addrbuf, sizeof(addrbuf));
dns_name_format(name, namebuf, sizeof(namebuf));
dns_rdatatype_format(rdataset->type, typebuf,
sizeof(typebuf));
dns_rdataclass_format(rdataset->rdclass, classbuf,
sizeof(classbuf));
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
"answer address %s denied for %s/%s/%s",
addrbuf, namebuf, typebuf, classbuf);
return (ISC_FALSE);
}
}
return (ISC_TRUE);
}
static isc_boolean_t
is_answertarget_allowed(dns_view_t *view, dns_name_t *name,
dns_rdatatype_t type, dns_name_t *tname,
dns_name_t *domain)
{
isc_result_t result;
dns_rbtnode_t *node = NULL;
char qnamebuf[DNS_NAME_FORMATSIZE];
char tnamebuf[DNS_NAME_FORMATSIZE];
char classbuf[64];
char typebuf[64];
/* By default, we allow any target name. */
if (view->denyanswernames == NULL)
return (ISC_TRUE);
/*
* If the owner name matches one in the exclusion list, either exactly
* or partially, allow it.
*/
if (view->answernames_exclude != NULL) {
result = dns_rbt_findnode(view->answernames_exclude, name, NULL,
&node, NULL, 0, NULL, NULL);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
return (ISC_TRUE);
}
/*
* If the target name is a subdomain of the search domain, allow it.
*/
if (dns_name_issubdomain(tname, domain))
return (ISC_TRUE);
/*
* Otherwise, apply filters.
*/
result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node,
NULL, 0, NULL, NULL);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
dns_name_format(name, qnamebuf, sizeof(qnamebuf));
dns_name_format(tname, tnamebuf, sizeof(tnamebuf));
dns_rdatatype_format(type, typebuf, sizeof(typebuf));
dns_rdataclass_format(view->rdclass, classbuf,
sizeof(classbuf));
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
DNS_LOGMODULE_RESOLVER, ISC_LOG_NOTICE,
"%s target %s denied for %s/%s",
typebuf, tnamebuf, qnamebuf, classbuf);
return (ISC_FALSE);
}
return (ISC_TRUE);
}
/*
* Handle a no-answer response (NXDOMAIN, NXRRSET, or referral).
* If bind8_ns_resp is ISC_TRUE, this is a suspected BIND 8
@ -5194,6 +5322,7 @@ answer_response(fetchctx_t *fctx) {
unsigned int aflag;
dns_rdatatype_t type;
dns_fixedname_t dname, fqname;
dns_view_t *view;
FCTXTRACE("answer_response");
@ -5216,6 +5345,7 @@ answer_response(fetchctx_t *fctx) {
aa = ISC_FALSE;
qname = &fctx->name;
type = fctx->type;
view = fctx->res->view;
result = dns_message_firstname(message, DNS_SECTION_ANSWER);
while (!done && result == ISC_R_SUCCESS) {
name = NULL;
@ -5236,6 +5366,18 @@ answer_response(fetchctx_t *fctx) {
*/
return (DNS_R_FORMERR);
}
/*
* Apply filters, if given, on answers to reject
* a malicious attempt of rebinding.
*/
if ((rdataset->type == dns_rdatatype_a ||
rdataset->type == dns_rdatatype_aaaa) &&
!is_answeraddress_allowed(view, name,
rdataset)) {
return (DNS_R_SERVFAIL);
}
if (rdataset->type == type && !found_cname) {
/*
* We've found an ordinary answer.
@ -5284,6 +5426,14 @@ answer_response(fetchctx_t *fctx) {
&tname);
if (result != ISC_R_SUCCESS)
return (result);
/* Apply filters on the target name. */
if (!is_answertarget_allowed(view,
name,
rdataset->type,
&tname,
&fctx->domain)) {
return (DNS_R_SERVFAIL);
}
} else if (rdataset->type == dns_rdatatype_rrsig
&& rdataset->covers ==
dns_rdatatype_cname
@ -5386,6 +5536,8 @@ answer_response(fetchctx_t *fctx) {
rdataset != NULL;
rdataset = ISC_LIST_NEXT(rdataset, link)) {
isc_boolean_t found_dname = ISC_FALSE;
dns_name_t *dname_name;
found = ISC_FALSE;
aflag = 0;
if (rdataset->type == dns_rdatatype_dname) {
@ -5415,6 +5567,15 @@ answer_response(fetchctx_t *fctx) {
return (result);
else
found_dname = ISC_TRUE;
dname_name = dns_fixedname_name(&dname);
if (!is_answertarget_allowed(view,
qname,
rdataset->type,
dname_name,
&fctx->domain)) {
return (DNS_R_SERVFAIL);
}
} else if (rdataset->type == dns_rdatatype_rrsig
&& rdataset->covers ==
dns_rdatatype_dname) {

View File

@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: view.c,v 1.153 2009/01/27 22:29:59 jinmei Exp $ */
/* $Id: view.c,v 1.154 2009/05/29 22:22:37 jinmei Exp $ */
/*! \file */
@ -40,6 +40,7 @@
#include <dns/masterdump.h>
#include <dns/order.h>
#include <dns/peer.h>
#include <dns/rbt.h>
#include <dns/rdataset.h>
#include <dns/request.h>
#include <dns/resolver.h>
@ -178,6 +179,10 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
view->notifyacl = NULL;
view->updateacl = NULL;
view->upfwdacl = NULL;
view->denyansweracl = NULL;
view->answeracl_exclude = NULL;
view->denyanswernames = NULL;
view->answernames_exclude = NULL;
view->requestixfr = ISC_TRUE;
view->provideixfr = ISC_TRUE;
view->maxcachettl = 7 * 24 * 3600;
@ -313,6 +318,14 @@ destroy(dns_view_t *view) {
dns_acl_detach(&view->updateacl);
if (view->upfwdacl != NULL)
dns_acl_detach(&view->upfwdacl);
if (view->denyansweracl != NULL)
dns_acl_detach(&view->denyansweracl);
if (view->answeracl_exclude != NULL)
dns_rbt_destroy(&view->answeracl_exclude);
if (view->denyanswernames != NULL)
dns_rbt_destroy(&view->denyanswernames);
if (view->answernames_exclude != NULL)
dns_rbt_destroy(&view->answernames_exclude);
if (view->delonly != NULL) {
dns_name_t *name;
int i;

View File

@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: namedconf.c,v 1.95 2009/03/04 02:42:31 each Exp $ */
/* $Id: namedconf.c,v 1.96 2009/05/29 22:22:37 jinmei Exp $ */
/*! \file */
@ -736,6 +736,34 @@ static cfg_type_t cfg_type_optional_exclude = {
"optional_exclude", parse_optional_keyvalue, print_keyvalue,
doc_optional_keyvalue, &cfg_rep_list, &exclude_kw };
static keyword_type_t exceptionnames_kw = { "except-from", &cfg_type_namelist };
static cfg_type_t cfg_type_optional_exceptionnames = {
"optional_allow", parse_optional_keyvalue, print_keyvalue,
doc_optional_keyvalue, &cfg_rep_list, &exceptionnames_kw };
static cfg_tuplefielddef_t denyaddresses_fields[] = {
{ "acl", &cfg_type_bracketed_aml, 0 },
{ "except-from", &cfg_type_optional_exceptionnames, 0 },
{ NULL, NULL, 0 }
};
static cfg_type_t cfg_type_denyaddresses = {
"denyaddresses", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple,
&cfg_rep_tuple, denyaddresses_fields
};
static cfg_tuplefielddef_t denyaliases_fields[] = {
{ "name", &cfg_type_namelist, 0 },
{ "except-from", &cfg_type_optional_exceptionnames, 0 },
{ NULL, NULL, 0 }
};
static cfg_type_t cfg_type_denyaliases = {
"denyaliases", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple,
&cfg_rep_tuple, denyaliases_fields
};
static cfg_type_t cfg_type_algorithmlist = {
"algorithmlist", cfg_parse_bracketed_list, cfg_print_bracketed_list,
cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_astring };
@ -813,6 +841,8 @@ view_clauses[] = {
{ "check-names", &cfg_type_checknames, CFG_CLAUSEFLAG_MULTI },
{ "cleaning-interval", &cfg_type_uint32, 0 },
{ "clients-per-query", &cfg_type_uint32, 0 },
{ "deny-answer-addresses", &cfg_type_denyaddresses, 0 },
{ "deny-answer-aliases", &cfg_type_denyaliases, 0 },
{ "disable-algorithms", &cfg_type_disablealgorithm,
CFG_CLAUSEFLAG_MULTI },
{ "disable-empty-zone", &cfg_type_astring, CFG_CLAUSEFLAG_MULTI },