Commit 937a2f47 authored by Petr Špaček's avatar Petr Špaček Committed by Vladimír Čunát

protection from DNS rebinding attack

Closes: #320
parent 377bc5cd
Pipeline #37370 passed with stages
in 7 minutes and 37 seconds
......@@ -6,6 +6,7 @@ New features
------------
- TLS session resumption (RFC 5077), both server and client (!585, #105)
- aggressive caching for NSEC3 zones
- optional protection from DNS Rebinding attack (module rebinding)
Bugfixes
--------
......
......@@ -295,6 +295,7 @@ int kr_pkt_clear_payload(knot_pkt_t *);
const char *kr_inaddr(const struct sockaddr *);
int kr_inaddr_family(const struct sockaddr *);
int kr_inaddr_len(const struct sockaddr *);
int kr_inaddr_str(const struct sockaddr *, char *, size_t *);
int kr_sockaddr_len(const struct sockaddr *);
uint16_t kr_inaddr_port(const struct sockaddr *);
int kr_straddr_family(const char *);
......
......@@ -155,6 +155,7 @@ EOF
kr_inaddr
kr_inaddr_family
kr_inaddr_len
kr_inaddr_str
kr_sockaddr_len
kr_inaddr_port
kr_straddr_family
......
......@@ -22,6 +22,7 @@ usr/lib/knot-resolver/predict.lua
usr/lib/knot-resolver/prefill.lua
usr/lib/knot-resolver/priming.lua
usr/lib/knot-resolver/prometheus.lua
usr/lib/knot-resolver/rebinding.lua
usr/lib/knot-resolver/renumber.lua
usr/lib/knot-resolver/serve_stale.lua
usr/lib/knot-resolver/ta_sentinel.lua
......
......@@ -15,6 +15,7 @@ Knot DNS Resolver modules
.. include:: ../modules/predict/README.rst
.. include:: ../modules/http/README.rst
.. include:: ../modules/daf/README.rst
.. include:: ../modules/rebinding/README.rst
.. include:: ../modules/graphite/README.rst
.. .. include:: ../modules/memcached/README.rst
.. .. include:: ../modules/redis/README.rst
......
......@@ -29,6 +29,7 @@ modules_TARGETS += etcd \
view \
predict \
dns64 \
rebinding \
renumber \
http \
daf \
......
.. _mod-rebinding:
Rebinding protection
--------------------
This module provides protection from `DNS Rebinding attack`_ by blocking
answers which cointain IPv4_ or IPv6_ addresses for private use
(or some other special-use addresses).
To enable this module insert following line into your configuration file:
.. code-block:: lua
modules.load('rebinding < iterate')
Please note that this module does not offer stable configuration interface
yet. For this reason it is suitable mainly for public resolver operators
who do not need to whitelist certain subnets.
.. warning:: Some like to "misuse" such addresses, e.g. `127.*.*.*`
in blacklists served over DNS, and this module will block such uses.
.. _`DNS Rebinding attack`: https://en.wikipedia.org/wiki/DNS_rebinding
.. _IPv4: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
.. _IPv6: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
-- Protection from DNS rebinding attacks
local kres = require('kres')
local renumber = require('renumber')
local M = {}
M.layer = {}
M.blacklist = {
-- https://www.iana.org/assignments/iana-ipv4-special-registry
-- + IPv4-to-IPv6 mapping
renumber.prefix('0.0.0.0/8', '0.0.0.0'),
renumber.prefix('::ffff:0.0.0.0/104', '::'),
renumber.prefix('10.0.0.0/8', '0.0.0.0'),
renumber.prefix('::ffff:10.0.0.0/104', '::'),
renumber.prefix('100.64.0.0/10', '0.0.0.0'),
renumber.prefix('::ffff:100.64.0.0/106', '::'),
renumber.prefix('127.0.0.0/8', '0.0.0.0'),
renumber.prefix('::ffff:127.0.0.0/104', '::'),
renumber.prefix('169.254.0.0/16', '0.0.0.0'),
renumber.prefix('::ffff:169.254.0.0/112', '::'),
renumber.prefix('172.16.0.0/12', '0.0.0.0'),
renumber.prefix('::ffff:172.16.0.0/108', '::'),
renumber.prefix('192.168.0.0/16', '0.0.0.0'),
renumber.prefix('::ffff:192.168.0.0/112', '::'),
-- https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
renumber.prefix('::/128', '::'),
renumber.prefix('::1/128', '::'),
renumber.prefix('fc00::/7', '::'),
renumber.prefix('fe80::/10', '::'),
} -- second parameter for renumber module is ignored except for being v4 or v6
local function is_rr_blacklisted(rr)
for i = 1, #M.blacklist do
local prefix = M.blacklist[i]
-- Match record type to address family and record address to given subnet
if renumber.match_subnet(prefix[1], prefix[2], prefix[4], rr) then
return true
end
end
return false
end
local function check_section(pkt, section)
local records = pkt:section(section)
local count = #records
if count == 0 then
return nil end
for i = 1, count do
local rr = records[i]
if rr.type == kres.type.A or rr.type == kres.type.AAAA then
local result = is_rr_blacklisted(rr)
if result then
return rr end
end
end
end
local function check_pkt(pkt)
for _, section in ipairs({kres.section.ANSWER,
kres.section.AUTHORITY,
kres.section.ADDITIONAL}) do
local bad_rr = check_section(pkt, section)
if bad_rr then
return bad_rr
end
end
end
local function refuse(req)
-- we are deleting packet in consume() phase so other modules
-- might have chosen some RRs from the original packet already
req.answ_selected.len = 0
req.auth_selected.len = 0
req.add_selected.len = 0
-- construct brand new answer packet
local pkt = req.answer
pkt:clear_payload()
pkt:rcode(kres.rcode.REFUSED)
pkt:ad(false)
pkt:aa(false)
pkt:begin(kres.section.ADDITIONAL)
local msg = 'blocked by DNS rebinding protection'
pkt:put('\11explanation\7invalid\0', 10800, pkt:qclass(), kres.type.TXT,
string.char(#msg) .. msg)
end
-- act on DNS queries which were not answered from cache
function M.layer.consume(state, req, pkt)
if state == kres.FAIL then
return state end
req = kres.request_t(req)
local qry = req:current()
if qry.flags.CACHED then -- do not slow down cached queries
return state end
pkt = kres.pkt_t(pkt)
local bad_rr = check_pkt(pkt)
if not bad_rr then
return state end
qry.flags.RESOLVED = 1 -- stop iteration
qry.flags.CACHED = 1 -- do not cache
refuse(req)
log('[' .. string.format('%5d', qry.id) .. '][rebinding] '
.. 'blocking blacklisted IP \'' .. kres.rr2str(bad_rr)
.. '\' received from IP ' .. tostring(kres.sockaddr_t(req.upstream.addr)))
return kres.FAIL
end
return M
rebinding_SOURCES := rebinding.lua
$(call make_lua_module,rebinding)
programs:
- name: kresd
binary: kresd
additional:
- -f
- "1"
templates:
- modules/rebinding/test.integr/kresd_config.j2
- tests/hints_zone.j2
configs:
- config
- hints
{% raw %}
-- Disable RFC8145 signaling, scenario doesn't provide expected answers
if ta_signal_query then
modules.unload('ta_signal_query')
end
-- Disable RFC8109 priming, scenario doesn't provide expected answers
if priming then
modules.unload('priming')
end
-- Disable this module because it make one priming query
if detect_time_skew then
modules.unload('detect_time_skew')
end
modules.load('rebinding < iterate')
_hint_root_file('hints')
cache.size = 2*MB
verbose(true)
{% endraw %}
net = { '{{SELF_ADDR}}' }
{% if QMIN == "false" %}
option('NO_MINIMIZE', true)
{% else %}
option('NO_MINIMIZE', false)
{% endif %}
-- Self-checks on globals
assert(help() ~= nil)
assert(worker.id ~= nil)
-- Self-checks on facilities
assert(cache.count() == 0)
assert(cache.stats() ~= nil)
assert(cache.backends() ~= nil)
assert(worker.stats() ~= nil)
assert(net.interfaces() ~= nil)
-- Self-checks on loaded stuff
assert(net.list()['{{SELF_ADDR}}'])
assert(#modules.list() > 0)
-- Self-check timers
ev = event.recurrent(1 * sec, function (ev) return 1 end)
event.cancel(ev)
ev = event.after(0, function (ev) return 1 end)
This diff is collapsed.
......@@ -106,6 +106,7 @@ local M = {
prefix = matchprefix,
name = matchname,
rule = rule,
match_subnet = match_subnet,
}
-- Config
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment