]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
2604. [func] Add support for DNS rebinding attack prevention through
authorTatuya JINMEI 神明達哉 <jinmei@isc.org>
Fri, 29 May 2009 22:22:37 +0000 (22:22 +0000)
committerTatuya JINMEI 神明達哉 <jinmei@isc.org>
Fri, 29 May 2009 22:22:37 +0000 (22:22 +0000)
new options, deny-answer-addresses and
deny-answer-aliases.  Based on contributed code from
JD Nurmi, Google. [RT #18192]

14 files changed:
CHANGES
README
bin/named/bind.keys.h
bin/named/server.c
bin/tests/system/resolver/ans2/ans.pl
bin/tests/system/resolver/ans3/ans.pl
bin/tests/system/resolver/clean.sh
bin/tests/system/resolver/ns1/named.conf
bin/tests/system/resolver/tests.sh
doc/arm/Bv9ARM-book.xml
lib/dns/include/dns/view.h
lib/dns/resolver.c
lib/dns/view.c
lib/isccfg/namedconf.c

diff --git a/CHANGES b/CHANGES
index 575b1baf40c64d91c4b9dedb0b336ec14e52cfb3..e52e42c52af5bcee8bbc6376ba9312707eec23a0 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -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]
diff --git a/README b/README
index 3b89403b64ef18dd09387630c154c155683edffc..8abc9d4065b8ebeb9bfa10d9805f5592e80ee86b 100644 (file)
--- a/README
+++ b/README
@@ -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
index 3486fc1fd97918c19d9fdace610056e48f1c521d..1b287a5184b9bf0863747da14cf749d35c08cf95 100644 (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\
 "
index 04a25361c1b14dc7388206d7bc7f78197449a017..0e25f9beb718115199b78a695c1fb25835830ed7 100644 (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,
index b41f1986b38366d19e9b7ea091d2bdbba98ba6d3..acfe4e1a3581fda213c39e5c514b80a4477b7aac 100644 (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"));
index 3053b25fe1a4f742a4c5b0ce8fc80786872fb0d3..3cc7f6858b043151000675c8dd9b7474229e410d 100644 (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);
 
index c79da928192a66e4eccc69657c67717bfea1cf87..8cc1b3b6723d5640be65dc16283b1ac8c46535d3 100644 (file)
 # 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
index 4b0c80abf9389e4cc1c6bd47f137767fa1491efd..8cc21e525f736fb92a922abc39bcea7c577fd201 100644 (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 "." {
index 585455c9a5715cb4a9ad9dc7aaf48009bda8f16c..a30e8e47752ec0cc76ce2de458af66562c3c9ce7 100644 (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
index 32d4c81ebe163ae8fadf1956edc0efd4a14acddd..56511a6e389ae301bf907d3e6e037317679330e7 100644 (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">
index 6c99ef1f7fa68f088e96ca28643ac435e6a7381a..d275b10fb69a6c4af36d314b18a23605496a28f7 100644 (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;
index a06c1eeb633ca1720e258504488eca4a3315898b..a1b12d388db45552f251ef71814439f416d3bd8f 100644 (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) {
index f7a3662916515e061063ec780227bad9ef47b903..b5414d4cd3ca0e0e87ac033452ca77bf4b477525 100644 (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;
index e2f4b45c846bb598d69efdded08b595cb2171ba0..5ec5b544c4ad0d6ab9c5727848d8bf6f080d6573 100644 (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 },