起死回生:使用简单的 Bash 应对复杂的 DdoS 攻击

作者:Greg Bledsoe

如果你在一家拥有在线业务的公司工作足够长的时间,你最终会遇到它。有人出于恶意、无聊、病态或这三者的某种组合,会以你的公司的在线业务和资源为攻击目标。如果你幸运的话,那将是一次普通的拒绝服务 (DoS) 攻击,来自单个或有限范围的 IP 地址,可以很容易地在你的最外层点被阻止,而攻击者将缺乏必要的专业知识来克服这种相对简单的对策。你通常遇到的脚本小子对具有 компетентной 网络和服务器管理能力的站点的攻击是相当短暂的。如果你不幸的话,你将遇到更糟糕的情况:一小部分攻击来自更高水平的黑客,虽然更难处理,但这些人通常很容易感到无聊并转移目标。

如果你非常非常不幸,某个技术高超且决心坚定的人会决定和你玩玩。如果这个人决定他们想破解进入你的服务器并探索你的环境,最终他们会得逞,你对此无能为力。只要他们不做太明显的事情,比如从你的服务器对其他站点发起大规模的字典破解攻击,即使你非常出色和细心,你也可能永远不会知道。如果他们决定把你从互联网上击倒,那么你就完蛋了。

我曾在一家不愿透露姓名的前雇主那里不幸地成为了这种攻击的受害者(但那是 2007 年,我的 LinkedIn 是公开的:http://www.linkedin.com/in/gregbledsoe)。似乎有人非常不喜欢我们,并决定将我们从网络世界中抹去。起初,这只是一个标准的 DoS syn-flood 攻击,任何脚本小子都可以发起,充其量只是一个小小的烦恼,通过在入口点阻止源 IP 可以很容易地缓解。然后事情变得有趣起来。

攻击者通过启用一个庞大的僵尸网络来适应,这变成了一次分布式拒绝服务 (DdoS) 攻击。目标服务器地址短暂宕机,直到我们联系运营商进一步阻止入站攻击。尽管如此,在那个时候,危机已经结束了,对吧?通常情况下,是的。但在这种情况下?还差得远呢。

攻击者*再次*调整了攻击,这次似乎轮流使用来自真实僵尸网络系统的连接,并且还从随机的欺骗 IP 地址发送了大量的虚假连接请求。总而言之,传入的连接请求数量一度接近一百万。这使我们彻底崩溃。恐慌随之而来,经过一番快速的头脑风暴,我们尝试了许多缓解技术,但都无济于事。连接穿过我们的防火墙,穿过我们的负载均衡器,并到达三个后端系统之一,所有这些系统都因处理攻击带来的负载而不堪重负。我们尝试在防火墙上使用速率限制,虽然我不确定他们具体实施了什么,但这导致了防火墙后面的所有东西都宕机,而不仅仅是目标 URL/服务器地址。速率限制语句被从配置中删除,但一切仍然处于宕机状态。我们发现防火墙设备内存不足,正在创建表空间来跟踪所有连接尝试。它无法区分欺骗的、真实的和合法的 tcp SYN 连接请求,因此它跟踪了所有请求并放行了它们。显然,我们使用的特定设备不允许更精细的速率限制。我们讨论了各种选择,包括重新调整我们的 DNS,将我们所有的流量都发送到一个(非常昂贵的)公司,该公司承诺在我们收到攻击之前对其进行清理。我对这个想法持怀疑态度。

作为 Unix 专家,我的领域是后端服务器,其次是负载均衡器。在观察了一段时间 netstats、lsof -ni's 和 tcpdumps 的输出后,我知道如何击败这次攻击。我花了大约 10 分钟制作我的对策,并在所有三台后端服务器上部署了它,几秒钟之内我们的环境就恢复了生机。Nagios 警报的红色在几分钟内消失了,我们的电话也不再响了。我们的总停机时间约为一小时。

我注意到使这种对策奏效的原因是,合法用户打开的连接数与来自作为攻击一部分的真实和欺骗 IP 的大量连接之间存在明显的阈值。通过在后端服务器上识别它们,并将 TCP 重置(带有 RST 标志)发送回所有超过阈值的连接请求,我们可以清除服务器、负载均衡器和防火墙上的连接信息,并释放用于在表中存储该条目的内存 - 足够快地清除足够多的连接,比新的攻击 IP 进入的速度更快,生活又恢复了美好。

这是我在所有三台服务器上运行的(非常简单)脚本。

#! /bin/bash
while [ 1 ] ;
 do
 for ip in `lsof -ni | grep httpd | grep -iv listen | awk '{print $8
}' | cut -d : -f 2 | sort | uniq | sed s/"http->"//` ;
 # the line above gets the list of all connections and connection
attempts, and produces a list of uniq IPs
 # and iterates through the list
  do
    noconns=`lsof -ni | grep $ip | wc -l`;
    # This finds how many connections there are from this particular IP address
    echo $ip : $noconns ;
    if [ "$noconns" -gt "10" ] ;
    # if there are more than 10 connections established or connecting
from this IP
    then
      # echo More;
      # echo `date` "$ip has $noconns connections.  Total connections
to prod spider:  `lsof -ni | grep httpd | grep -iv listen | wc -l`" >>
/var/log/Ddos/Ddos.log
      # to keep track of the IPs uncomment the above two lines and
make sure you can write to the appropriate place
      iptables -I INPUT -s $ip -p tcp -j REJECT --reject-with tcp-reset
      # for these connections, add an iptables statement to send
resets on any packets recieved
    else
        # echo Less;
    fi;
  done
sleep 60
done

我们的攻击者多次尝试适应这个解决方案,例如试图让僵尸网络的某些部分从某个 IP(如 1.1.1.1)开始,并尽可能快地轮流通过 IP 地址发送一个连接,以避免触发阈值,但无法足够快地轮流以造成与以前相同程度的破坏。这个脚本被证明对他的其余攻击非常有效。进行了一些微调,例如在行老化到一定程度后删除行,但脚本的本质保持不变。

我真正喜欢这个解决方案的地方是它的简洁性。我发现最好的解决方案通常是最简单的。如果你真正理解底层的技术和协议,那么你通常可以看穿问题的本质,并避免在你的环境中添加一层又一层的费用和复杂性(以及相应的断点)。

我非常愿意在 GPL v2 许可下发布它。如果有人有兴趣将此代码片段或概念整合到更大的分发解决方案中,请通过下面的电子邮件地址告诉我。

加载 Disqus 评论