Compare commits

...

8 Commits

Author SHA1 Message Date
Artem Baskal
a9efc39306 querylog: Add DNS rebinding protection 2020-12-08 18:11:01 +03:00
Reinaldo de Souza Jr
f924523f6a Extract method Server.setRebindingConfig() 2020-12-08 11:42:20 +01:00
Reinaldo de Souza Jr
6e6ee9697a Fix data race condition in test 2020-12-08 11:42:20 +01:00
Reinaldo de Souza Jr
aff09211b2 Refactor dnsRebindChecker.isRebindIP() 2020-12-08 11:42:20 +01:00
Reinaldo de Souza Jr
bad1c6acdc Use urlfilter format in rebinding allow list 2020-12-08 11:42:20 +01:00
Reinaldo de Souza Jr
fcb582679e Create UI for DNS rebinding protection 2020-12-08 11:42:20 +01:00
Reinaldo de Souza Jr
6b60598025 Process DNS rebinding protection after DNSSEC 2020-12-08 11:42:20 +01:00
Reinaldo de Souza Jr
b338bf9b3f Check for DNS rebinding attacks 2020-12-08 11:42:20 +01:00
20 changed files with 658 additions and 42 deletions

View File

@@ -1558,6 +1558,7 @@ Strict matching can be enabled by enclosing the value in double quotes: e.g. `"a
* blocked_services - blocked services
* blocked_safebrowsing - blocked by safebrowsing
* blocked_parental - blocked by parental control
* blocked_dns_rebinding - blocked by DNS rebinding protection
* whitelisted - whitelisted
* rewritten - all kinds of rewrites
* safe_search - enforced safe search

View File

@@ -587,5 +587,12 @@
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client.",
"client_not_in_allowed_clients": "The client is not allowed because it is not in the \"Allowed clients\" list.",
"experimental": "Experimental"
"experimental": "Experimental",
"rebinding_title": "DNS Rebinding Protection",
"rebinding_desc": "Here you can configure protection against DNS rebinding attacks",
"rebinding_protection_enabled": "Enable protection from DNS rebinding attacks",
"rebinding_protection_enabled_desc": "If enabled, AdGuard Home will block responses containing host on the local network.",
"rebinding_allowed_hosts_title": "Allowed domains",
"rebinding_allowed_hosts_desc": "A list of domains. If configured, AdGuard Home will allow responses containing host on the local network from these domains. Here you can specify the exact domain names, wildcards and urlfilter-rules, e.g. 'example.org', '*.example.org' or '||example.org^'.",
"blocked_dns_rebinding": "Blocked DNS rebinding"
}

View File

@@ -37,6 +37,10 @@ export const setDnsConfig = (config) => async (dispatch) => {
data.upstream_dns = splitByNewLine(config.upstream_dns);
hasDnsSettings = true;
}
if (Object.prototype.hasOwnProperty.call(data, 'rebinding_allowed_hosts')) {
data.rebinding_allowed_hosts = splitByNewLine(config.rebinding_allowed_hosts);
hasDnsSettings = true;
}
await apiClient.setDnsConfig(data);

View File

@@ -0,0 +1,91 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { renderTextareaField, CheckboxField } from '../../../../helpers/form';
import { removeEmptyLines } from '../../../../helpers/helpers';
import { FORM_NAME } from '../../../../helpers/constants';
const fields = [
{
id: 'rebinding_allowed_hosts',
title: 'rebinding_allowed_hosts_title',
subtitle: 'rebinding_allowed_hosts_desc',
normalizeOnBlur: removeEmptyLines,
},
];
const Form = ({
handleSubmit, submitting, invalid,
}) => {
const { t } = useTranslation();
const processingSetConfig = useSelector((state) => state.dnsConfig.processingSetConfig);
const renderField = ({
id, title, subtitle, disabled = processingSetConfig, normalizeOnBlur,
}) => <div key={id} className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor={id}>
<Trans>{title}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{subtitle}</Trans>
</div>
<Field
id={id}
name={id}
component={renderTextareaField}
type="text"
className="form-control form-control--textarea font-monospace"
disabled={disabled}
normalizeOnBlur={normalizeOnBlur}
/>
</div>;
renderField.propTypes = {
id: PropTypes.string,
title: PropTypes.string,
subtitle: PropTypes.string,
disabled: PropTypes.bool,
normalizeOnBlur: PropTypes.func,
};
return (
<form onSubmit={handleSubmit}>
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name={'rebinding_protection_enabled'}
type="checkbox"
component={CheckboxField}
placeholder={t('rebinding_protection_enabled')}
subtitle={t('rebinding_protection_enabled_desc')}
disabled={processingSetConfig}
/>
</div>
</div>
{fields.map(renderField)}
<div className="card-actions">
<div className="btn-list">
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || invalid || processingSetConfig}
>
<Trans>save_config</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
};
export default reduxForm({ form: FORM_NAME.REBINDING })(Form);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Form from './Form';
import Card from '../../../ui/Card';
import { setDnsConfig } from '../../../../actions/dnsConfig';
const RebindingConfig = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
rebinding_protection_enabled, rebinding_allowed_hosts,
} = useSelector((state) => state.dnsConfig, shallowEqual);
const handleFormSubmit = (values) => {
dispatch(setDnsConfig(values));
};
return (
<Card
title={t('rebinding_title')}
subtitle={t('rebinding_desc')}
bodyType="card-body box-body--settings"
>
<Form
initialValues={{
rebinding_protection_enabled,
rebinding_allowed_hosts,
}}
onSubmit={handleFormSubmit}
/>
</Card>
);
};
export default RebindingConfig;

View File

@@ -8,6 +8,7 @@ import Config from './Config';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
import CacheConfig from './Cache';
import RebindingConfig from './Rebinding';
import { getDnsConfig } from '../../../actions/dnsConfig';
import { getAccessList } from '../../../actions/access';
@@ -33,6 +34,7 @@ const Dns = () => {
<Config />
<CacheConfig />
<Access />
<RebindingConfig />
</>}
</>;
};

View File

@@ -341,6 +341,7 @@ export const FILTERED_STATUS = {
REWRITE_HOSTS: 'RewriteEtcHosts',
FILTERED_SAFE_SEARCH: 'FilteredSafeSearch',
FILTERED_SAFE_BROWSING: 'FilteredSafeBrowsing',
FILTERED_REBIND: 'FilteredRebind',
FILTERED_PARENTAL: 'FilteredParental',
};
@@ -373,6 +374,10 @@ export const RESPONSE_FILTER = {
QUERY: 'blocked_parental',
LABEL: 'blocked_adult_websites',
},
BLOCKED_DNS_REBINDING: {
QUERY: 'blocked_dns_rebinding',
LABEL: 'blocked_dns_rebinding',
},
ALLOWED: {
QUERY: 'whitelisted',
LABEL: 'allowed',
@@ -414,6 +419,10 @@ export const FILTERED_STATUS_TO_META_MAP = {
LABEL: 'blocked_service',
COLOR: QUERY_STATUS_COLORS.RED,
},
[FILTERED_STATUS.FILTERED_REBIND]: {
LABEL: RESPONSE_FILTER.BLOCKED_DNS_REBINDING.LABEL,
COLOR: QUERY_STATUS_COLORS.RED,
},
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
LABEL: RESPONSE_FILTER.SAFE_SEARCH.LABEL,
COLOR: QUERY_STATUS_COLORS.YELLOW,
@@ -509,6 +518,7 @@ export const FORM_NAME = {
INSTALL: 'install',
LOGIN: 'login',
CACHE: 'cache',
REBINDING: 'rebinding',
...DHCP_FORM_NAMES,
};

View File

@@ -16,6 +16,7 @@ const dnsConfig = handleActions(
blocking_ipv6,
upstream_dns,
bootstrap_dns,
rebinding_allowed_hosts,
...values
} = payload;
@@ -24,8 +25,9 @@ const dnsConfig = handleActions(
...values,
blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4,
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
upstream_dns: upstream_dns?.join('\n') || '',
bootstrap_dns: bootstrap_dns?.join('\n') || '',
rebinding_allowed_hosts: rebinding_allowed_hosts?.join('\n') || '',
processingGetConfig: false,
};
},

View File

@@ -146,6 +146,8 @@ const (
FilteredSafeSearch
// FilteredBlockedService - the host is blocked by "blocked services" settings
FilteredBlockedService
// FilteredRebind - the request was blocked due to DNS rebinding protection
FilteredRebind
// ReasonRewrite - rewrite rule was applied
ReasonRewrite
@@ -165,6 +167,7 @@ var reasonNames = []string{
"FilteredInvalid",
"FilteredSafeSearch",
"FilteredBlockedService",
"FilteredRebind",
"Rewrite",
"RewriteEtcHosts",

View File

@@ -76,6 +76,11 @@ type FilteringConfig struct {
CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server
CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server
// DNS rebinding protection settings
// --
RebindingProtectionEnabled bool `yaml:"rebinding_protection_enabled"`
RebindingAllowedHosts []string `yaml:"rebinding_allowed_hosts"`
// Other settings
// --

View File

@@ -48,6 +48,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
processFilteringBeforeRequest,
processUpstream,
processDNSSECAfterResponse,
processRebindingFilteringAfterResponse,
processFilteringAfterResponse,
s.ipset.process,
processQueryLogsAndStats,

View File

@@ -53,6 +53,7 @@ type Server struct {
queryLog querylog.QueryLog // Query log instance
stats stats.Stats
access *accessCtx
rebinding *dnsRebindChecker
ipset ipsetCtx
@@ -122,6 +123,7 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
c.DisallowedClients = stringArrayDup(sc.DisallowedClients)
c.BlockedHosts = stringArrayDup(sc.BlockedHosts)
c.UpstreamDNS = stringArrayDup(sc.UpstreamDNS)
c.RebindingAllowedHosts = stringArrayDup(sc.RebindingAllowedHosts)
s.RUnlock()
}
@@ -221,6 +223,13 @@ func (s *Server) Prepare(config *ServerConfig) error {
return err
}
// Initialize DNS rebinding module
// --
s.rebinding, err = newRebindChecker(s.conf.RebindingAllowedHosts)
if err != nil {
return err
}
// Register web handlers if necessary
// --
if !webRegistered && s.conf.HTTPRegister != nil {

View File

@@ -738,6 +738,100 @@ 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.Lock()
s.conf.RebindingProtectionEnabled = true
s.Unlock()
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.Lock()
s.conf.RebindingProtectionEnabled = false
s.Unlock()
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.Lock()
s.conf.RebindingProtectionEnabled = true
s.rebinding, _ = newRebindChecker([]string{
"||nip.io^",
})
s.Unlock()
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^

View File

@@ -113,8 +113,8 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (*dnsfilter.Result, error) {
switch v := a.(type) {
case *dns.CNAME:
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
host = strings.TrimSuffix(v.Target, ".")
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
case *dns.A:
host = v.A.String()

View File

@@ -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,8 @@ func (s *Server) setConfig(dc dnsConfig) (restart bool) {
s.conf.FastestAddr = false
}
}
restart = restart || s.setRebindingConfig(dc)
s.Unlock()
s.conf.ConfigModified()
return restart

View File

@@ -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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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}\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\":[\"\"]}",

View File

@@ -0,0 +1,227 @@
// DNS Rebinding protection
package dnsforward
import (
"fmt"
"net"
"strings"
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter"
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/miekg/dns"
)
type dnsRebindChecker struct {
allowDomainEngine *urlfilter.DNSEngine
}
func newRebindChecker(allowedHosts []string) (*dnsRebindChecker, error) {
buf := strings.Builder{}
for _, s := range allowedHosts {
buf.WriteString(s)
buf.WriteString("\n")
}
rulesStorage, err := filterlist.NewRuleStorage([]filterlist.RuleList{
&filterlist.StringRuleList{
ID: int(0),
RulesText: buf.String(),
IgnoreCosmetic: true,
},
})
if err != nil {
return nil, err
}
return &dnsRebindChecker{
allowDomainEngine: urlfilter.NewDNSEngine(rulesStorage),
}, nil
}
func (c *dnsRebindChecker) isAllowedDomain(domain string) bool {
_, ok := c.allowDomainEngine.Match(domain)
return ok
}
// IsPrivate reports whether ip is a private address, according to
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
func (*dnsRebindChecker) isPrivate(ip net.IP) bool {
//TODO: remove once https://github.com/golang/go/pull/42793 makes it to stdlib
if ip4 := ip.To4(); ip4 != nil {
return ip4[0] == 10 ||
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
(ip4[0] == 192 && ip4[1] == 168)
}
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
}
func (c *dnsRebindChecker) isRebindHost(host string) bool {
if ip := net.ParseIP(host); ip != nil {
return c.isRebindIP(ip)
}
return host == "localhost"
}
func (c *dnsRebindChecker) isLocalNetworkV4(ip4 net.IP) bool {
switch {
case ip4[0] == 0:
/* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
case ip4[0] == 10:
/* 10.0.0.0/8 (private) */
case ip4[0] == 172 && ip4[1]&0x10 == 0x10:
/* 172.16.0.0/12 (private) */
case ip4[0] == 169 && ip4[1] == 254:
/* 169.254.0.0/16 (zeroconf) */
case ip4[0] == 192 && ip4[1] == 0 && ip4[2] == 2:
/* 192.0.2.0/24 (test-net) */
case ip4[0] == 198 && ip4[1] == 51 && ip4[2] == 100:
/* 198.51.100.0/24(test-net) */
case ip4[0] == 203 && ip4[1] == 0 && ip4[2] == 113:
/* 203.0.113.0/24 (test-net) */
case ip4.Equal(net.IPv4bcast):
/* 255.255.255.255/32 (broadcast)*/
default:
return false
}
return true
}
func (c *dnsRebindChecker) isLocalNetworkV6(ip6 net.IP) bool {
return ip6.Equal(net.IPv6zero) ||
ip6.Equal(net.IPv6unspecified) ||
ip6.Equal(net.IPv6interfacelocalallnodes) ||
ip6.Equal(net.IPv6linklocalallnodes) ||
ip6.Equal(net.IPv6linklocalallrouters)
}
func (c *dnsRebindChecker) isRebindIP(ip net.IP) bool {
// This is compatible with dnsmasq definition
// See: https://github.com/imp/dnsmasq/blob/4e7694d7107d2299f4aaededf8917fceb5dfb924/src/rfc1035.c#L412
rebind := false
if ip4 := ip.To4(); ip4 != nil {
rebind = c.isLocalNetworkV4(ip4)
} else {
rebind = c.isLocalNetworkV6(ip)
}
return rebind || c.isPrivate(ip) || ip.IsLoopback()
}
// 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.RebindingProtectionEnabled {
return false
}
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("DNS Rebinding check for %s -> %s", domain, host)
}
if s.rebinding.isAllowedDomain(domain) {
return false
}
return s.rebinding.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) setRebindingConfig(dc dnsConfig) bool {
restart := false
if dc.RebindingProtectionEnabled != nil {
s.conf.RebindingProtectionEnabled = *dc.RebindingProtectionEnabled
}
if dc.RebindingAllowedHosts != nil {
s.conf.RebindingAllowedHosts = *dc.RebindingAllowedHosts
restart = true
}
return restart
}
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(strings.TrimSuffix(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
}

View File

@@ -0,0 +1,113 @@
package dnsforward
import (
"math/rand"
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRebindingPrivateAddresses(t *testing.T) {
c, _ := newRebindChecker(nil)
r1 := byte(rand.Int31() & 0xFE)
r2 := byte(rand.Int31() & 0xFE)
r3 := byte(rand.Int31() & 0xFE)
for _, ip := range []net.IP{
net.IPv4(0, r1, r2, r3), /* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
net.IPv4(127, r1, r2, r3), /* 127.0.0.0/8 (loopback) */
net.IPv4(10, r1, r2, r3), /* 10.0.0.0/8 (private) */
net.IPv4(172, 0x10|byte(1&rand.Int31()), r2, r3), /* 172.16.0.0/12 (private) */
net.IPv4(192, 168, r2, r3), /* 192.168.0.0/16 (private) */
net.IPv4(169, 254, r2, r3), /* 169.254.0.0/16 (zeroconf) */
net.IPv4(192, 0, 2, r3), /* 192.0.2.0/24 (test-net) */
net.IPv4(198, 51, 100, r3), /* 198.51.100.0/24(test-net) */
net.IPv4(203, 0, 113, r3), /* 203.0.113.0/24 (test-net) */
net.IPv4(255, 255, 255, 255), /* 255.255.255.255/32 (broadcast)*/
/* RFC 6303 4.3 (unspecified & loopback) */
net.IPv6zero,
net.IPv6unspecified,
/* RFC 6303 4.4 */
/* RFC 6303 4.5 */
/* RFC 6303 4.6 */
net.IPv6interfacelocalallnodes,
net.IPv6linklocalallnodes,
net.IPv6linklocalallrouters,
/* (TODO) Check IPv4-mapped IPv6 addresses */
} {
assert.Truef(t, c.isRebindIP(ip), "%s is not a rebind", ip)
}
}
func TestRebindLocalhost(t *testing.T) {
c := &dnsRebindChecker{}
assert.False(t, c.isRebindHost("example.com"))
assert.False(t, c.isRebindHost("200.0.0.1"))
assert.True(t, c.isRebindHost("127.0.0.1"))
assert.True(t, c.isRebindHost("localhost"))
}
func TestIsResponseRebind(t *testing.T) {
c, _ := newRebindChecker([]string{
"||totally-safe.com^",
})
s := &Server{
rebinding: c,
}
for _, host := range []string{
"0.1.2.3", /* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
"127.1.2.3", /* 127.0.0.0/8 (loopback) */
"10.1.2.3", /* 10.0.0.0/8 (private) */
"172.16.2.3", /* 172.16.0.0/12 (private) */
"192.168.2.3", /* 192.168.0.0/16 (private) */
"169.254.2.3", /* 169.254.0.0/16 (zeroconf) */
"192.0.2.3", /* 192.0.2.0/24 (test-net) */
"198.51.100.3", /* 198.51.100.0/24(test-net) */
"203.0.113.3", /* 203.0.113.0/24 (test-net) */
"255.255.255.255", /* 255.255.255.255/32 (broadcast)*/
/* RFC 6303 4.3 (unspecified & loopback) */
net.IPv6zero.String(),
net.IPv6unspecified.String(),
/* RFC 6303 4.4 */
/* RFC 6303 4.5 */
/* RFC 6303 4.6 */
net.IPv6interfacelocalallnodes.String(),
net.IPv6linklocalallnodes.String(),
net.IPv6linklocalallrouters.String(),
"localhost",
} {
s.conf.RebindingProtectionEnabled = true
assert.Truef(t, s.isResponseRebind("example.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("absolutely.totally-safe.com", host), "host: %s", host)
s.conf.RebindingProtectionEnabled = false
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("absolutely.totally-safe.com", host), "host: %s", host)
}
for _, host := range []string{
"200.168.2.3",
"another-example.com",
} {
s.conf.RebindingProtectionEnabled = true
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("absolutely.totally-legit.com", host), "host: %s", host)
s.conf.RebindingProtectionEnabled = false
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
assert.Falsef(t, s.isResponseRebind("absolutely.totally-legit.com", host), "host: %s", host)
}
}

View File

@@ -17,14 +17,15 @@ const (
filteringStatusAll = "all"
filteringStatusFiltered = "filtered" // all kinds of filtering
filteringStatusBlocked = "blocked" // blocked or blocked services
filteringStatusBlockedService = "blocked_services" // blocked
filteringStatusBlockedSafebrowsing = "blocked_safebrowsing" // blocked by safebrowsing
filteringStatusBlockedParental = "blocked_parental" // blocked by parental control
filteringStatusWhitelisted = "whitelisted" // whitelisted
filteringStatusRewritten = "rewritten" // all kinds of rewrites
filteringStatusSafeSearch = "safe_search" // enforced safe search
filteringStatusProcessed = "processed" // not blocked, not white-listed entries
filteringStatusBlocked = "blocked" // blocked or blocked services
filteringStatusBlockedService = "blocked_services" // blocked
filteringStatusBlockedSafebrowsing = "blocked_safebrowsing" // blocked by safebrowsing
filteringStatusBlockedParental = "blocked_parental" // blocked by parental control
filteringStatusBlockedRebind = "blocked_dns_rebinding" // blocked by DNS rebinding protection
filteringStatusWhitelisted = "whitelisted" // whitelisted
filteringStatusRewritten = "rewritten" // all kinds of rewrites
filteringStatusSafeSearch = "safe_search" // enforced safe search
filteringStatusProcessed = "processed" // not blocked, not white-listed entries
)
// filteringStatusValues -- array with all possible filteringStatus values
@@ -32,7 +33,7 @@ var filteringStatusValues = []string{
filteringStatusAll, filteringStatusFiltered, filteringStatusBlocked,
filteringStatusBlockedService, filteringStatusBlockedSafebrowsing, filteringStatusBlockedParental,
filteringStatusWhitelisted, filteringStatusRewritten, filteringStatusSafeSearch,
filteringStatusProcessed,
filteringStatusProcessed, filteringStatusBlockedRebind,
}
// searchCriteria - every search request may contain a list of different search criteria

View File

@@ -190,6 +190,7 @@
- 'blocked'
- 'blocked_safebrowsing'
- 'blocked_parental'
- 'blocked_dns_rebinding'
- 'whitelisted'
- 'rewritten'
- 'safe_search'