旁路劫持攻

来源:互联网 发布:sql数据脱敏处理方法 编辑:程序博客网 时间:2024/04/27 20:54

旁路劫持攻击简介

在client和server之间的网络通信中,如果它们之间交互的流量被攻击者监听到,那么攻击者就可以监听client端发起的请求,然后伪造一个假的response包发送给client端。假如攻击者与受害主机的网络距离(物理跳数)小于真实服务器与受害者的距离,那么这个伪造的response就会比真实服务器发出的响应提前到达,从而使客户端优先处理了这个假的response,而真的response因为来的晚,最后被client端忽略掉了。

黑客通过旁路劫持攻击,可以达到很多目的。比如在用户访问的网页中插入一段恶意的js代码,骗取账号密码;或者将页面重定向到精心准备好的钓鱼页面;也可以将大量的用户请求重定向到一个第三方的网站,当访问量很大时,可以达到ddos的效果。

从实现的角度来说,可以构造一个假的http页面的response,这个页面里面包含了一段恶意代码,但是其余部分和真实的网站内容一致,这样对于受害者来说,虽然看起来访问的网站和真实的一致,但是恶意代码其实已经在悄悄的运行了。或者利用http协议的302跳转,受害者的浏览器在收到这样的response之后,会自动跳转到攻击者指定的url,这样就能够实现钓鱼攻击,或者用来ddos目标url。当然也可以直接监听客户的dns请求,然后发一假的response过去,这样受害者就会直接访问你指定的那个ip地址,同样可以实现钓鱼攻击,或者ddos攻击。

以http劫持为例,一个简单的劫持流程说明如下:


1.        受害者向真实服务器发起一个http get请求

2.        受害者通过控制网络中的交换机或其它服务器,监听到了受害者的请求

3.        攻击者构造了一个假的response包,内容为302跳转,目的地址为黑客指定的地址

4.        受害者在收到攻击者发出的响应后,自动跳转到了www.hack.com,攻击成功

5.        因为真实服务器发出的response在这之后才到达,直接被tcp协议丢弃了。

劫持成功的条件:

要劫持成功,首先伪造的response包要满足几个基本条件

1.        伪造包的seq,和ack要与真实的response一致。这两个值可以根据监听到的get请求计算出来。(实测情况下,这两个值其实是可以变动的,也能劫持成功)

2.        伪造包的源/目的ip,源/目的端口号要符合当前会话要求

3.        目的mac地址必须是受害者的mac地址

4.        Tcp校验和之类的东西,否则受害者收到这个包之后,校验失败就丢掉了

5.        伪造包一定要比正常的response先到达


测试环境下,使用scapy模拟旁路劫持:


为了模拟受害者流量被监听的环境,这里使用交换机将交换机的流量镜像一份出来,用来运行监听脚本的为一个有双网卡的ubuntu虚拟机,监听脚本从eth1口读取镜像过来的流量,如果发现有发往知乎的http请求,那么就根据当前的请求生成一个伪造的response从eth0口发出去。这里因为攻击者和受害者都是在一个交换机上,而且受害者要访问的服务器是公网服务器,所以正常情况下伪造包肯定是会比真实的response先到达受害pc的。

功能通过scapy实现起来比较简单,首先是在eth1上监听镜像过来的流量,设置过滤条件,如果发现有发往www.zhihu.com的请求,那么就调用函数构造一个伪造包,然后从eth0口发出去。伪造包的内容为一个302跳转,目的地址为一个内网的url。

import scapyfrom scapy.all import *response = "HTTP/1.1 302 FOUND\r\nServer: nginx/1.1.15\r\nContent-Type: text/html; charset=utf-8\r\nConnection: keep-alive\r\nLocation: http://10.16.66.167/\r\n\r\n"def gen_res(x):        p_E = Ether()        p_E.src = x[Ether].dst        p_E.dst = x[Ether].src        p_I = IP()        p_I.src = x[IP].dst        p_I.dst = x[IP].src        p_I.flags = x[IP].flags        p_I.ttl = 234        data_len = x[IP].len - 40        p_T = TCP()        p_T.sport = x[TCP].dport        p_T.dport = x[TCP].sport        p_T.flags = x[TCP].flags        p_T.seq = x[TCP].ack        p_T.ack = x[TCP].seq + data_len        p_R = Raw(response)        p = Ether(str(p_E/p_I/p_T/p_R))        print ls(p)        sendp(p,iface = "eth0")def pr(x):        print ls(x)        gen_res(x)my_filter = lambda(r): Raw in r and TCP in r and r[TCP].dport == 80 and "Host: www.zhihu.com" in r[Raw].loadpkts = sniff(iface = "eth1",lfilter = my_filter,count = 0, prn = pr)~                                                                     

伪造包中seq和ack的取值

正常情况下,伪造包中的seq和监听到的get请求中的ack一致,伪造包中的ack等于get请求包中的seq加上这个包的tcp playload长度。实际测试的情况下,攻击者在发送伪造包时,seq和ack并非严格按照这个要求才能成功。

假如我们监听到的get请求的seq和ack,tcppayload长度分别为get_seq,get_ack,get_len

1.        伪造包ack的取值fake_ack:

当fake_ack > get_seq + get_len,这个时候不能劫持成功。大概的原因就是,客户端发出的数据最大序列为get_seq+get_len,这个时候fake_ack大于这个值的话,就相当于服务器告诉客户端,我收到的数据比你真实发出来的内容还多,所以这个情况下,客户端就会认为这是一个异常包,直接丢掉了。

 

当fake_ack <= get_seq + get_len,这个时候是可以劫持成功的。但是没有试过fake_ack小于这个会话最初始的seq的情况,估计是不行的。

2.        伪造包的seq的取值fake_seq:

当fake_seq<get_ack,或者fake_seq>get_ack很多时,同样不能劫持成功,这种情况下有些时候浏览器会直接把http response里面的header直接打印出来。

为了验证为什么这种情况下不能劫持成功,写了一个简单的tcp socket程序来发送给请求,然后把收到的response打印出来。

最后得到的结论是,当伪造的fake_seq != get_ack时,tcp会把这个伪造的response和真实的response合并为一个response返回给浏览器。假的response内容会按照seq序列,覆盖掉真实response内容的一部分。

当fake_seq = get_ack-1时,我们得到的response是这样:

注意红色部分内容实际是我们伪造的response,但是少了第一个字符h,剩下的部分为真实的response,只是前面的数据被覆盖掉了(至于到底谁覆盖谁,好像不同的操作系统上实现方式是不一样的,测试环境下用的是windows)。这种情况下,浏览器收到这样的response,是不会正常解析的。

 

当fake_seq=get_ack+1时,我们得到的response是这样的:


左边是通过wireshark查看到的response,右边是通过tcp socket获得的resonse,而且实际情况是,浏览器跳转到了我指定的地址。所以这里应该以右侧的结果作为参考。至于为什么wireshark呈现的是这样,应该和它对tcp的实现方式,以及本身的配置有关系。我们看到绿色部分多出来了一个h,就是因为伪造包的seq向右偏移了一个字节导致的。这种情况下,浏览器还可以正常解析这个response,够跳转。可是如果伪造包的seq向右偏移太多的话,就会导致最后浏览器收到的response本身格式被破坏,所以就不能正常解析并实现跳转了。

0 0