diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index dbdc15b8..e25209b6 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -78,8 +78,8 @@ type FilteringConfig struct { // DNS rebinding protection settings // -- - RebindingEnabled bool `yaml:"rebinding_enabled"` - RebindingAllowedHosts []string `yaml:"rebinding_allowed_hosts"` + RebindingProtectionEnabled bool `yaml:"rebinding_protection_enabled"` + RebindingAllowedHosts []string `yaml:"rebinding_allowed_hosts"` // Other settings // -- diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 0f9c764b..b520c781 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -48,6 +48,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error { processFilteringBeforeRequest, processUpstream, processDNSSECAfterResponse, + processRebindingFilteringAfterResponse, processFilteringAfterResponse, s.ipset.process, processQueryLogsAndStats, diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 327fbe82..af94462a 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -738,6 +738,91 @@ func TestRewrite(t *testing.T) { _ = s.Stop() } +func TestBlockedDNSRebinding(t *testing.T) { + s := createTestServer(t) + + err := s.Start() + if err != nil { + t.Fatalf("Failed to start server: %s", err) + } + addr := s.dnsProxy.Addr(proxy.ProtoUDP) + + // + // DNS rebinding protection + // + req := dns.Msg{} + req.Id = dns.Id() + req.RecursionDesired = true + req.Question = []dns.Question{ + {Name: "192-168-1-250.nip.io.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, + } + + s.conf.RebindingProtectionEnabled = true + reply, err := dns.Exchange(&req, addr.String()) + if err != nil { + t.Fatalf("Couldn't talk to server %s: %s", addr, err) + } + + if len(reply.Answer) != 1 { + t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) + } + + a, ok := reply.Answer[0].(*dns.A) + if !ok { + t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) + } + + if !net.IPv4zero.Equal(a.A) { + t.Fatalf("DNS server %s returned wrong answer instead of 0.0.0.0: %v", addr, a.A) + } + + s.conf.RebindingProtectionEnabled = false + reply, err = dns.Exchange(&req, addr.String()) + if err != nil { + t.Fatalf("Couldn't talk to server %s: %s", addr, err) + } + + if len(reply.Answer) != 1 { + t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) + } + + a, ok = reply.Answer[0].(*dns.A) + if !ok { + t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) + } + + if !net.IPv4(192, 168, 1, 250).Equal(a.A) { + t.Fatalf("DNS server %s returned wrong answer instead of 192.168.1.250: %v", addr, a.A) + } + + s.conf.RebindingProtectionEnabled = true + s.conf.RebindingAllowedHosts = []string{ + "nip.io.", + } + reply, err = dns.Exchange(&req, addr.String()) + if err != nil { + t.Fatalf("Couldn't talk to server %s: %s", addr, err) + } + + if len(reply.Answer) != 1 { + t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer)) + } + + a, ok = reply.Answer[0].(*dns.A) + if !ok { + t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0]) + } + + if !net.IPv4(192, 168, 1, 250).Equal(a.A) { + t.Fatalf("DNS server %s returned wrong answer instead of 192.168.1.250: %v", addr, a.A) + } + + err = s.Stop() + if err != nil { + t.Fatalf("DNS server failed to stop: %s", err) + } +} + func createTestServer(t *testing.T) *Server { rules := `||nxdomain.example.org ||null.example.org^ diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index c6c33c4e..1537ebc8 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -37,6 +37,9 @@ type dnsConfig struct { CacheSize *uint32 `json:"cache_size"` CacheMinTTL *uint32 `json:"cache_ttl_min"` CacheMaxTTL *uint32 `json:"cache_ttl_max"` + + RebindingProtectionEnabled *bool `json:"rebinding_protection_enabled"` + RebindingAllowedHosts *[]string `json:"rebinding_allowed_hosts"` } func (s *Server) getDNSConfig() dnsConfig { @@ -61,23 +64,27 @@ func (s *Server) getDNSConfig() dnsConfig { } else if s.conf.AllServers { upstreamMode = "parallel" } + rebindingEnabled := s.conf.RebindingProtectionEnabled + rebindingAllowedHosts := stringArrayDup(s.conf.RebindingAllowedHosts) s.RUnlock() return dnsConfig{ - Upstreams: &upstreams, - UpstreamsFile: &upstreamFile, - Bootstraps: &bootstraps, - ProtectionEnabled: &protectionEnabled, - BlockingMode: &blockingMode, - BlockingIPv4: &BlockingIPv4, - BlockingIPv6: &BlockingIPv6, - RateLimit: &Ratelimit, - EDNSCSEnabled: &EnableEDNSClientSubnet, - DNSSECEnabled: &EnableDNSSEC, - DisableIPv6: &AAAADisabled, - CacheSize: &CacheSize, - CacheMinTTL: &CacheMinTTL, - CacheMaxTTL: &CacheMaxTTL, - UpstreamMode: &upstreamMode, + Upstreams: &upstreams, + UpstreamsFile: &upstreamFile, + Bootstraps: &bootstraps, + ProtectionEnabled: &protectionEnabled, + BlockingMode: &blockingMode, + BlockingIPv4: &BlockingIPv4, + BlockingIPv6: &BlockingIPv6, + RateLimit: &Ratelimit, + EDNSCSEnabled: &EnableEDNSClientSubnet, + DNSSECEnabled: &EnableDNSSEC, + DisableIPv6: &AAAADisabled, + CacheSize: &CacheSize, + CacheMinTTL: &CacheMinTTL, + CacheMaxTTL: &CacheMaxTTL, + UpstreamMode: &upstreamMode, + RebindingProtectionEnabled: &rebindingEnabled, + RebindingAllowedHosts: &rebindingAllowedHosts, } } @@ -301,6 +308,14 @@ func (s *Server) setConfig(dc dnsConfig) (restart bool) { s.conf.FastestAddr = false } } + + if dc.RebindingProtectionEnabled != nil { + s.conf.RebindingProtectionEnabled = *dc.RebindingProtectionEnabled + } + + if dc.RebindingAllowedHosts != nil { + s.conf.RebindingAllowedHosts = *dc.RebindingAllowedHosts + } s.Unlock() s.conf.ConfigModified() return restart diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index fa071bcf..80de236c 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -29,7 +29,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf: func() ServerConfig { return defaultConf }, - want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "fastest_addr", conf: func() ServerConfig { @@ -37,7 +37,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf.FastestAddr = true return conf }, - want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "parallel", conf: func() ServerConfig { @@ -45,7 +45,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf.AllServers = true return conf }, - want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }} for _, tc := range testCases { @@ -73,7 +73,7 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) { w := httptest.NewRecorder() - const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n" + const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n" testCases := []struct { name string req string @@ -83,52 +83,52 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) { name: "upstream_dns", req: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "bootstraps", req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "blocking_mode_good", req: "{\"blocking_mode\":\"refused\"}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "blocking_mode_bad", req: "{\"blocking_mode\":\"custom_ip\"}", wantSet: "blocking_mode: incorrect value\n", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "ratelimit", req: "{\"ratelimit\":6}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "edns_cs_enabled", req: "{\"edns_cs_enabled\":true}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "dnssec_enabled", req: "{\"dnssec_enabled\":true}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "cache_size", req: "{\"cache_size\":1024}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "upstream_mode_parallel", req: "{\"upstream_mode\":\"parallel\"}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "upstream_mode_fastest_addr", req: "{\"upstream_mode\":\"fastest_addr\"}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n", }, { name: "upstream_dns_bad", req: "{\"upstream_dns\":[\"\"]}", diff --git a/internal/dnsforward/rebind.go b/internal/dnsforward/rebind.go index 9484e107..f629ff05 100644 --- a/internal/dnsforward/rebind.go +++ b/internal/dnsforward/rebind.go @@ -3,10 +3,13 @@ package dnsforward import ( + "fmt" "net" "strings" + "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/golibs/log" + "github.com/miekg/dns" ) type dnsRebindChecker struct { @@ -75,7 +78,7 @@ func (c *dnsRebindChecker) isRebindIP(ip net.IP) bool { // Checks DNS rebinding attacks // Note both whitelisted and cached hosts will bypass rebinding check (see: processFilteringAfterResponse()). func (s *Server) isResponseRebind(domain, host string) bool { - if !s.conf.RebindingEnabled { + if !s.conf.RebindingProtectionEnabled { return false } @@ -93,3 +96,82 @@ func (s *Server) isResponseRebind(domain, host string) bool { c := dnsRebindChecker{} return c.isRebindHost(host) } + +func processRebindingFilteringAfterResponse(ctx *dnsContext) int { + s := ctx.srv + d := ctx.proxyCtx + res := ctx.result + var err error + + if !ctx.responseFromUpstream || res.Reason == dnsfilter.ReasonRewrite { + return resultDone + } + + originalRes := d.Res + ctx.result, err = s.preventRebindResponse(ctx) + if err != nil { + ctx.err = err + return resultError + } + if ctx.result != nil { + ctx.origResp = originalRes // matched by response + } else { + ctx.result = &dnsfilter.Result{} + } + + return resultDone +} + +func (s *Server) preventRebindResponse(ctx *dnsContext) (*dnsfilter.Result, error) { + d := ctx.proxyCtx + + for _, a := range d.Res.Answer { + m := "" + domainName := "" + host := "" + + switch v := a.(type) { + case *dns.CNAME: + host = strings.TrimSuffix(v.Target, ".") + domainName = v.Hdr.Name + m = fmt.Sprintf("DNSRebind: Checking CNAME %s for %s", v.Target, v.Hdr.Name) + + case *dns.A: + host = v.A.String() + domainName = v.Hdr.Name + m = fmt.Sprintf("DNSRebind: Checking record A (%s) for %s", host, v.Hdr.Name) + + case *dns.AAAA: + host = v.AAAA.String() + domainName = v.Hdr.Name + m = fmt.Sprintf("DNSRebind: Checking record AAAA (%s) for %s", host, v.Hdr.Name) + + default: + continue + } + + s.RLock() + if !s.conf.RebindingProtectionEnabled { + s.RUnlock() + continue + } + + log.Debug(m) + blocked := s.isResponseRebind(domainName, host) + s.RUnlock() + + if blocked { + res := &dnsfilter.Result{ + IsFiltered: true, + Reason: dnsfilter.FilteredRebind, + Rule: "adguard-rebind-protection", + } + + d.Res = s.genDNSFilterMessage(d, res) + log.Debug("DNSRebind: Matched %s by response: %s", d.Req.Question[0].Name, host) + return res, nil + } + } + + return nil, nil +} diff --git a/internal/dnsforward/rebind_test.go b/internal/dnsforward/rebind_test.go index 5535631c..51bd4395 100644 --- a/internal/dnsforward/rebind_test.go +++ b/internal/dnsforward/rebind_test.go @@ -83,12 +83,12 @@ func TestIsResponseRebind(t *testing.T) { "localhost", } { - s.conf.RebindingEnabled = true + s.conf.RebindingProtectionEnabled = true assert.True(t, s.isResponseRebind("example.com", host)) assert.False(t, s.isResponseRebind("totally-safe.com", host)) assert.False(t, s.isResponseRebind("absolutely.totally-safe.com", host)) - s.conf.RebindingEnabled = false + s.conf.RebindingProtectionEnabled = false assert.False(t, s.isResponseRebind("example.com", host)) assert.False(t, s.isResponseRebind("totally-safe.com", host)) assert.False(t, s.isResponseRebind("absolutely.totally-safe.com", host)) @@ -98,12 +98,12 @@ func TestIsResponseRebind(t *testing.T) { "200.168.2.3", "another-example.com", } { - s.conf.RebindingEnabled = true + s.conf.RebindingProtectionEnabled = true assert.False(t, s.isResponseRebind("example.com", host)) assert.False(t, s.isResponseRebind("totally-safe.com", host)) assert.False(t, s.isResponseRebind("absolutely.totally-legit.com", host)) - s.conf.RebindingEnabled = false + s.conf.RebindingProtectionEnabled = false assert.False(t, s.isResponseRebind("example.com", host)) assert.False(t, s.isResponseRebind("totally-safe.com", host)) assert.False(t, s.isResponseRebind("absolutely.totally-legit.com", host))