一种抵御 DDoS 攻击的 IP 追踪技术

目录 互联网, 安全

在拾掇家务时,发现一页我在 2008 年 10 月做的备忘录,记录了一个可用于抵御 DDoS 攻击的 IP 追踪技术。当时因为觉得 idea 太小,不值当写成文章投出去。纸张放那里总是占地方,在博客里电子化一下,然后就能销毁了。

Differential Deterministic Packet Marking

这个 idea 是在 IP 协议的基础上做一些扩展,可以帮助用户在 DDoS 攻击时识别攻击数据包和定位攻击者。在理解这个 idea 之前,可能需要先看几篇参考文献:

[1] Z. Gao and N. Ansari, "Tracing cyber attacks from the practical perspective, " IEEE Communications Magazine, vol. 43, no. 5, pp. 123-131, 2005.

[2] A. Belenky and N. Ansari, "On deterministic packet marking, " Computer Networks, vol. 51, no. 10, pp. 2677–2700, 2007.

[3] Y. Xiang, W, Zhou, and M. Guo, "Flexible deterministic packet marking: an iP traceback system to find the real source of attacks, " IEEE Transactions on Parallel and Distributed Systems, vol. 20, no. 4, pp. 567-580, 2009.

这个 idea 主要是在 [3] 基础上做的改进,其 motivation 是仅仅使用标记段 [3] 内容太保守,用标记段再结合 IP 头中已有的信息,可以做得更好。简单来说就是运营商的接入路由器在 IP 头中增加一些标记,服务器在遭遇到 DDoS 攻击时,可以根据接入路由器增加的标记再结合 IP 头中已有的信息,识别攻击流量,以及确认攻击源。

下文的内容基于三个假设:

  1. Source IP 和 ingress router 的接入 interface 的 IP 经常在同一个网段中;
  2. 大部分网络流是正常的网络流而非 DDoS 的网络流。
  3. ingress router 在可控域中,未被入侵。
图1: 典型网络拓扑示意图,来源于[2]

由图中可见,如果主机使用真实IP 的话,Host 1 发出数据包的 source IP 和 router interface 1 的 IP 仅在最后八位不同,Host 2 发出数据包的 IP 和 router interface 2 的IP 也是仅仅在最后八位不同。

由假设有大部分数据包的 source IP 与 router interface IP 在同一个网段中,这样 ingress router 只需在标记段中标记与 source IP 不同的位即可进行追踪。

图2: IP 头和标记段,标记段与[3]相同

在我的 idea 里,Mark 使用的是 IP 头中的 IDENTIFICATION 域,共 16 位(我们也可以用上 TOS,这样就有 19 或者 24 位),其中各个位的作用如图 3 所示:

图3: 标记段内容

入口路由器上执行的标记算法是:

Algorithm:( 16-bit Mark case, RI for Router Interface)

if (SourceAddr RIAddr ) ⊕ & 0xffff8000 = 0 // Case 1
    Mark := SourceAddr ⊕ RIAddr // 1 packet
else if (SourceAddr ⊕ RIAddr ) & 0xff000000 = 0 // Case 2
    Digest := hash(RIAddr)
    for i=0 to 2 // 3 packets
        Mark[i].M := 1
        Mark[i].A := 0
        Mark[i].seg := i
        Mark[i].digest := Digest
        Mark[i].address_diff := ((SourceAddr ⊕ RIAddr) >> (i*8)) & 0xff
else // Case 3
    Digest := hash(RIAddr)
    for i=0 to 3 // 4 packets
        Mark[i].M := 1
        Mark[i].A := 1
        Mark[i].seg := i
        Mark[i].digest := Digest
        Mark[i].address_diff := ( RIAddr >> (i*8)) & 0xff

我提出的新 idea 对 [3] 的改进能够带来以下几个好处:

  1. 大部分数据包仅靠 1 个包就可以 traceback。根据上面的假设,正常的数据包应该仅仅在 IP 地址的低位与路由器接口 IP 不同,这样仅仅需要在 mark 中的低 15 位与源 IP 异或就能得到路由器接口 IP(case 1)。
  2. 加快路由器对正常数据包的处理速度。由于正常的数据包仅需要 1 个包就能 traceback,不需要计算地址的 hash 值,大大加快了路由器对正常数据包,也是大部分数据包的处理速度。
  3. 不影响正常 IP 数据包的 fragment 策略。由于大部分正常的数据包仅仅需要一个包就能 traceback,那么修改 IDENTIFICATION 域对这些数据包的 fragment 策略毫无影响。对于非正常的数据包,本算法需要多个包才能 traceback,所以对其 fragment 策略会有影响,但由于它是非正常的数据包,不用考虑后果。

之所以觉得这个 idea 小,有两个原因:

  • 第一是在别人方案基础上做的改进,创新不够;
  • 第二是需要部署到全网(至少某个运营商内部)所有接入路由器上,(尤其在 IPv6 已增强安全性的前提下)不太可能实现。

这也符合我对很多研究的看法,虽然有意义,但在工程上基本价值不大,基本上就自己 YY 一下。

心中一块大石落地

目录 安全, 无线和移动

从找到工作以来,我就在忙论文,因为中科院的硕士毕业是要发表论文的。还有半年临近毕业,已经火烧眉毛了,因此我一直生活在无法按时毕业的恐慌中,也不愿意在博客上多讲,可以发现我最近两个月的更新非常少。

今天是中期答辩的日子。因为没有文章,我很怕答辩时候被枪毙掉,那我只能延期毕业了。但是,昨天正在反复修改答辩幻灯片的时候,收到了一条天大的好消息,我投往 ICC 2010 的一篇文章被录用了,而且还是 oral session!ICC,IEEE 通信学会的旗舰会议啊,我这个小硕实在是太满足了,太满足了,顷刻间,我内牛满面!

再看看审稿意见,我不得不赞叹我的运气。三个审稿人,4444,4444,4434;修改意见里,一个是“没特别的修改意见”,一个是“文章太干巴,应该生动些”,只有最后一个提出了一个切实的修改意见。看到这样友好的评审结果,我又一次内牛满面!

于是乎,两篇论文,一篇已录用,我的中期答辩顺利通过。从目前来看,毕业已经不成问题,谢天谢地!我也总算不用再羞羞答答,敢挺直腰板说俺也是搞过科研的人了。

解决 GAppProxy Set-Cookie 和 HTTPS Cert Bugs

目录 安全, 开源

我自己写了一个类似 GAppProxy 的工具,支持 Python 和 PHP,有兴趣可以看这里

研究 GAppProxy 有两个原因:一、最近 Twitter 不能用,而我常用的 GAppProxy 却不支持我登录 Twitter;二、我最近在琢磨 SSL 证书的问题,正好用 GAppProxy 登录 Twitter 也有证书错误。

第一个 BUG:Set-Cookie Bug

GAPPProxy 目前对 Cookie 的处理有一些问题,主要出在对 header 中的多个 Set-Cookie 域处理错误,就会导致用户登录一些网站错误,无法获得正确的会话 Cookie。

举例,当服务器返回的 header 中有多个 Set-Cookie 域时,比如一般的 wordpress 返回的 header 中,Set-Cookie 域至少有三个:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly
Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly
Set-Cookie:
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

GAPPProxy 会把它作为一个串传给本地浏览器:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly,
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly,
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

这样本地浏览器对 Cookie 的设置就会错误。解决办法很简单,将这个长串用split(', ')切开,同样设置三个 Set-Cookie 域即可。

Update 20090710/Solrex:
有人评论说 ', ' 也是可能在 Cookie 中出现的合法字符串;那么我就另外想了一个办法,先用正则表达式替换将 ', ***=' 替换成 'n***=',再用 'n' 对字符串进行切割。由于在 Cookie 中正常出现的 ', ' 后面会首先跟着 ';' 或 ',',然后才可能出现 =,因此用 ‘, ([^,;]+=)’ 匹配就可以了。而且这次把修改放到服务器端了,原来的客户端就不需要修改了

Patch:

Index: fetchserver/fetch.py
===================================================================
--- fetchserver/fetch.py    (revision 92)
+++ fetchserver/fetch.py    (working copy)
@@ -29,6 +29,7 @@
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
from google.appengine.api import urlfetch_errors
+import re
# from accesslog import logAccess

@@ -153,14 +154,12 @@
             if header.strip().lower() in self.HtohHdrs:
                 # don't forward
                 continue
-            ## there may have some problems on multi-cookie process in urlfetch.
-            #if header.lower() == 'set-cookie':
-            #    logging.info('O %s: %s' % (header, resp.headers[header]))
-            #    scs = resp.headers[header].split(',')
-            #    for sc in scs:
-            #        logging.info('N %s: %s' % (header, sc.strip()))
-            #        self.response.out.write('%s: %srn' % (header, sc.strip()))
-            #    continue
+            # NOTE 20090710/Solrex: Fix multi-cookie process problem
+            if header.lower() == 'set-cookie':
+                scs = re.sub(r', ([^,;]+=)', r'n1', resp.headers[header]).split('n')
+                for sc in scs:
+                    self.response.out.write('%s: %srn' % (header, sc.strip()))
+                continue
             # other
             self.response.out.write('%s: %srn' % (header, resp.headers[header]))
             # check Content-Type

第二个 BUG:HTTPS Cert Bug

简单地来说,GAppProxy HTTPS 连接的实现是一个欺骗本地浏览器的过程,类似于中间人攻击。它首先用 GAE 获得页面的明文,抓到本地,然后假冒 HTTPS 站点与本地浏览器通信。因此它就需要提供一个 SSL 证书,来完成 HTTPS 连接的建立。

但这就有一个问题,SSL 证书哪儿来的?目前 GAppProxy 对所有的站点都使用一个证书,而且这个证书是未经任何授权 CA 认证的证书,因此就会产生很多错误。首先,该证书中的授权机构不是可信 CA,Firefox 和 IE 中捆绑的证书中没有该 CA 的证书;其次,该证书的 CN (Common Name)与站点域名不同,不可用于站点的通信。因此可能每次都需要用户自己点击添加证书例外。

为了避免这种缺陷,我想出来的做法是——自己做 CA,正所谓做事情要专业,要骗就骗彻底点,骗得浏览器神不知鬼不觉。首先,建立 CA 的密钥,自己给自己签发一个证书作为 CA 的根证书,将该 CA 的证书安装到 Firefox 浏览器中,在运行时使用该 CA 为每个 HTTPS 连接的站点签发对应于该站点的证书。由于 Firefox 中已经安装了该 CA 的证书,那么所有该 CA 签发的证书都能够通过 Firefox 的检测了。

这个方法比较麻烦,而且需要电脑上有 openssl,对于 Linux 完全没有问题,对于 Windows 可能就有点儿困难了。所以我这里就给出个思路,具体实现就不谈了。
您可以在 http://share.solrex.org/ibuild/ 找到我修改后的代码,感兴趣的话可以下载下来看看。