mirror of
https://github.com/isc-projects/bind9.git
synced 2026-07-15 01:40:48 -04:00
fix: usr: Fix a 'deny-answer-aliases' configuration bypass issue
It was possible to use a maliciously crafted authoritative zone to make :iscman:`named` resolver synthesize a ``DNAME`` "alias" that should have been rejected by the configured :any:`deny-answer-aliases` option. This has been fixed. Closes #5930 Merge branch '5930-deny-answer-aliases-and-cached-dname-buf-fix' into 'main' See merge request isc-projects/bind9!12044
This commit is contained in:
commit
50dbab9340
4 changed files with 107 additions and 2 deletions
|
|
@ -141,6 +141,7 @@ class Ns2Delegation(DelegationHandler):
|
|||
class Ns3Delegation(DelegationHandler):
|
||||
domains = [
|
||||
"example.net.",
|
||||
"isc.org.",
|
||||
"lame.example.org.",
|
||||
"sub.example.org.",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -41,13 +41,18 @@ from ..resolver_ans import (
|
|||
)
|
||||
|
||||
|
||||
class ApexNSHandler(QnameHandler, StaticResponseHandler):
|
||||
class ApexNSHandler(QnameQtypeHandler, StaticResponseHandler):
|
||||
qnames = ["example.net."]
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.NS, f"ns.{qnames[0]}")]
|
||||
additional = [rrset(f"ns.{qnames[0]}", dns.rdatatype.A, "10.53.0.3")]
|
||||
|
||||
|
||||
class AttackDnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["www.example.attack.example.net", "isc.attack.example.net."]
|
||||
answer = [rrset("attack.example.net.", dns.rdatatype.DNAME, "org.")]
|
||||
|
||||
|
||||
class BadCnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["badcname.example.net."]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.CNAME, "badcname.example.org.")]
|
||||
|
|
@ -64,6 +69,12 @@ class CnameSubHandler(QnameHandler, StaticResponseHandler):
|
|||
answer = [rrset(qnames[0], dns.rdatatype.CNAME, "ok.sub.example.org.")]
|
||||
|
||||
|
||||
class ExampleOrgHandler(QnameQtypeHandler, StaticResponseHandler):
|
||||
qnames = ["example.org."]
|
||||
qtypes = [dns.rdatatype.A]
|
||||
answer = [rrset(qnames[0], qtypes[0], "1.2.3.4")]
|
||||
|
||||
|
||||
class FooBadDnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["foo.baddname.example.net."]
|
||||
answer = [
|
||||
|
|
@ -93,12 +104,18 @@ class GoodCnameHandler(QnameHandler, StaticResponseHandler):
|
|||
answer = [rrset(qnames[0], dns.rdatatype.CNAME, "goodcname.example.org.")]
|
||||
|
||||
|
||||
class IscHandler(QnameQtypeHandler, StaticResponseHandler):
|
||||
qnames = ["isc.org."]
|
||||
qtypes = [dns.rdatatype.A]
|
||||
answer = [rrset(qnames[0], qtypes[0], "1.2.3.4")]
|
||||
|
||||
|
||||
class LameExampleOrgDelegation(DelegationHandler):
|
||||
domains = ["lame.example.org."]
|
||||
server_number = 3
|
||||
|
||||
|
||||
class LargeReferralHandler(QnameHandler, StaticResponseHandler):
|
||||
class LargeReferralHandler(QnameQtypeHandler, StaticResponseHandler):
|
||||
qnames = ["large-referral.example.net."]
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
authority = [
|
||||
|
|
@ -172,6 +189,11 @@ class WwwDnameSubHandler(QnameHandler, StaticResponseHandler):
|
|||
]
|
||||
|
||||
|
||||
class WwwGoodDnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["www.example.gooddname.example.net"]
|
||||
answer = [rrset("gooddname.example.net.", dns.rdatatype.DNAME, "org.")]
|
||||
|
||||
|
||||
class WwwHandler(QnameHandler):
|
||||
qnames = ["www.example.net."]
|
||||
|
||||
|
|
@ -195,9 +217,11 @@ def main() -> None:
|
|||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
ApexNSHandler(),
|
||||
AttackDnameHandler(),
|
||||
BadCnameHandler(),
|
||||
BadGoodDnameNsHandler(),
|
||||
CnameSubHandler(),
|
||||
ExampleOrgHandler(),
|
||||
FooBadDnameHandler(),
|
||||
FooBarSubTld1Handler(),
|
||||
FooGoodDnameHandler(),
|
||||
|
|
@ -207,6 +231,7 @@ def main() -> None:
|
|||
Gl6412Ns2Handler(),
|
||||
Gl6412Ns3Handler(),
|
||||
GoodCnameHandler(),
|
||||
IscHandler(),
|
||||
LameExampleOrgDelegation(),
|
||||
LargeReferralHandler(),
|
||||
LongCnameHandler(),
|
||||
|
|
@ -217,6 +242,7 @@ def main() -> None:
|
|||
OkSubHandler(),
|
||||
PartialFormerrHandler(),
|
||||
WwwDnameSubHandler(),
|
||||
WwwGoodDnameHandler(),
|
||||
WwwHandler(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
import time
|
||||
|
||||
import dns.message
|
||||
|
||||
import isctest
|
||||
|
||||
|
||||
|
|
@ -40,3 +42,46 @@ def test_resolver_cache_reloadfails(ns1, templates):
|
|||
# The ttl being lower than 300 (provided by fake authoritative) proves
|
||||
# the cache is still in use
|
||||
assert res.answer[0].ttl < 300
|
||||
|
||||
|
||||
# GL#5930
|
||||
def test_resolver_dname_target_filter_attack():
|
||||
# Control check - this should return 'attack.example.net. DNAME org.',
|
||||
# which then should result in resolving 'www.example.org. AAAA', which
|
||||
# should be SERVAIL because example.org is in 'deny-answer-aliases'.
|
||||
msg = isctest.query.create("www.example.attack.example.net.", "AAAA")
|
||||
res = isctest.query.udp(msg, "10.53.0.1")
|
||||
isctest.check.servfail(res)
|
||||
|
||||
# Execute the attack - this should return 'attack.example.net. DNAME org.',
|
||||
# which then should result in resolving isc.org and caching the DNAME.
|
||||
msg = isctest.query.create("isc.attack.example.net.", "A")
|
||||
res = isctest.query.udp(msg, "10.53.0.1")
|
||||
answer = """;ANSWER
|
||||
attack.example.net. 300 IN DNAME org.
|
||||
isc.attack.example.net. 300 IN CNAME isc.org.
|
||||
isc.org. 300 IN A 1.2.3.4
|
||||
;AUTHORITY
|
||||
;ADDITIONAL
|
||||
"""
|
||||
expected_answer = dns.message.from_text(answer)
|
||||
isctest.check.noerror(res)
|
||||
isctest.check.rrsets_equal(res.answer, expected_answer.answer)
|
||||
isctest.check.rrsets_equal(res.authority, expected_answer.authority)
|
||||
isctest.check.rrsets_equal(res.additional, expected_answer.additional)
|
||||
|
||||
# Vulnerability check - this should return 'attack.example.net. DNAME org.'
|
||||
# which then should result in resolving 'www.example.org. A', which
|
||||
# should still be SERVAIL because example.org is in 'deny-answer-aliases',
|
||||
# unless the attack on the previous step was successful.
|
||||
msg = isctest.query.create("www.example.attack.example.net.", "A")
|
||||
res = isctest.query.udp(msg, "10.53.0.1")
|
||||
isctest.check.servfail(res)
|
||||
|
||||
# Exception check - this should return 'gooddname.example.net. DNAME org.'
|
||||
# which then should result in resolving 'www.example.org. A', which
|
||||
# should be NOERROR because while example.org is in 'deny-answer-aliases',
|
||||
# gooddname.example.net is in the exceptions list.
|
||||
msg = isctest.query.create("www.example.gooddname.example.net.", "A")
|
||||
res = isctest.query.udp(msg, "10.53.0.1")
|
||||
isctest.check.noerror(res)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include <dns/ede.h>
|
||||
#include <dns/keytable.h>
|
||||
#include <dns/message.h>
|
||||
#include <dns/nametree.h>
|
||||
#include <dns/ncache.h>
|
||||
#include <dns/nsec.h>
|
||||
#include <dns/nsec3.h>
|
||||
|
|
@ -10137,6 +10138,7 @@ query_cname(query_ctx_t *qctx) {
|
|||
dns_name_copy(&cname.cname, tname);
|
||||
|
||||
dns_rdata_freestruct(&cname);
|
||||
|
||||
ns_client_qnamereplace(qctx->client, tname);
|
||||
qctx->want_restart = true;
|
||||
if (!WANTRECURSION(qctx->client)) {
|
||||
|
|
@ -10260,6 +10262,37 @@ query_dname(query_ctx_t *qctx) {
|
|||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the target is a denied alias, and both the `except-from` list
|
||||
* and the subdomain rule of the `deny-answer-aliases`
|
||||
* configuration option (see ARM) don't give an exception, then
|
||||
* answer with a SERVFAIL.
|
||||
*/
|
||||
dns_fixedname_t fdeniedname;
|
||||
dns_name_t *deniedname = dns_fixedname_initname(&fdeniedname);
|
||||
if (qctx->view->denyanswernames != NULL &&
|
||||
dns_nametree_covered(qctx->view->denyanswernames, qctx->fname,
|
||||
deniedname, 0) &&
|
||||
!dns_nametree_covered(qctx->view->answernames_exclude,
|
||||
qctx->client->query.qname, NULL, 0) &&
|
||||
!dns_name_issubdomain(qctx->client->query.qname, deniedname))
|
||||
{
|
||||
char qnamebuf[DNS_NAME_FORMATSIZE];
|
||||
char tnamebuf[DNS_NAME_FORMATSIZE];
|
||||
|
||||
dns_name_format(qctx->client->query.qname, qnamebuf,
|
||||
sizeof(qnamebuf));
|
||||
dns_name_format(qctx->fname, tnamebuf, sizeof(tnamebuf));
|
||||
ns_client_log(qctx->client, NS_LOGCATEGORY_QUERIES,
|
||||
NS_LOGMODULE_QUERY, ISC_LOG_NOTICE,
|
||||
"DNAME target %s denied for %s (cache)", tnamebuf,
|
||||
qnamebuf);
|
||||
QUERY_ERROR(qctx, DNS_R_SERVFAIL);
|
||||
ns_client_releasename(qctx->client, &qctx->fname);
|
||||
(void)ns_query_done(qctx);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in a new issue