Web Administration Scripts—Redux

作者:Dave Taylor

已经好几个月了,我仍然在处理服务器上的 DDOS(分布式拒绝服务)攻击——我可以看到这种攻击来自中国,但除了尝试调整防火墙设置之外,我真的做不了什么。

在我的上一篇文章中,我开始描述分析 Apache 日志文件和系统设置,以尝试创建一些脚本,这些脚本可以监视 DDOS 攻击并在不可避免地收到客户关于站点宕机或速度极慢的电子邮件之前标记它们。

老实说,处理这些匿名攻击真是令人沮丧,但那是另一篇文章的主题了。如果您有处理此类长期问题的经验,我很想听听您最终是如何解决的。

我开发的第一个脚本只是跟踪服务器上运行的进程数量。这只是从监视 uptime 程序的输出迈出了一小步,当然,这可以像这样轻松完成


load="$(uptime | cut -d\  -f14 | cut -d. -f1)"
if [ $load -gt 4 ] ; then
  echo "Uptime greater than threshold. What's going on?"
  uptime
fi

这有点意思,但让我们回到分析正在运行的进程数量,因为这更好地表明可能正在发生 DDOS。上次,我使用了 ps ; sleep 循环并跟踪了最小值/最大值。运行一段时间后,脚本的输出看起来像这样


Current ps count=90: min=76, max=150, tally=70 and average=107

将其理解为在运行 90 个周期后,任何给定时间运行的 Apache (httpd) 进程的最小数量为 76,最大数量为 150,平均数量为 107。

一旦你运行这个脚本几个小时,你就会对服务器上的典型流量负载有一个很好的了解,这对于你能够检测到超出模式的变化至关重要。如果我的平均 Apache 进程计数是 107,而服务器有 917 个进程,那就说明有问题。另一方面,如果平均负载是 325 个进程,那么 600 个进程并没有超出范围太远,并且可能代表流量的激增,而不是实际攻击的开始。

我在上个月的文章结尾展示了一个适合从 cron 运行的脚本,该脚本将查找异常的流量峰值。但它有一些问题,正如我在专栏结尾强调的那样,这是我的书呆子版本的悬念——没有未解决的谋杀案,没有车道上的汽车,没有警笛声,只是一些需要改进的代码。嘿,配合一下!

脚本的核心问题实际上是缺乏上下文。也就是说,我不想在第一次看到异常高的进程计数时就收到通知,而是在连续第三次或第四次时才收到通知。然后,一旦它通知了我,我希望脚本足够智能,除非情况稳定下来然后再次跳升,否则不要再次通知我。

上下文。

对于某些脚本,这种事情可能非常棘手,需要临时文件和信号量来确保脚本不会踩到同时读取/写入文件的另一个版本。这是可行的,但你真的必须考虑最坏的情况、暂时阻塞的 I/O 通道等等。

幸运的是,这里的情况并非如此。事实上,你可以通过添加几个状态变量来完成上下文的添加。让我们首先拉回监控脚本


#!/bin/sh
# DDOS - keep an eye on process count to 
# detect a blossoming DDOS attack
pattern="httpd"
max=200   # avoid false positives
admin="d1taylor@gmail.com"
count="$(ps -C $pattern | wc -l)"
if [ $count -gt $max ] ; then
  echo "Warning: DDOS in process? Current httpd 
  ↪count = $count" | sendmail
$admin
fi
exit 0

让我们使用一个“repeated”变量,并将其设置为在第四次出现过多进程后发送通知。这只是更改条件语句的问题


if [ $count -gt $max ] ; then
  repeated=$(( $repeated + 1 ))
  if [ $repeated -eq 4 ] ; then
    echo "Warning: DDOS in process? Current httpd count
    = $count" | sendmail $admin
  fi
fi

不太难。但是,当迭代次数不超过最大阈值时会发生什么?如果你愿意保持冗余地将 repeated 设置为零,这也很容易处理。外部的“fi”只需更改为


else
  repeated=0
fi

这些添加实际上产生了两个期望的更新,因为 repeated 确保它不会在问题发生几次后才通知,并且条件 $repeated -eq 4 而不是,例如,$repeated -gt 4,也意味着即使它被猛击了 15 次迭代,你仍然只会看到一条电子邮件消息。

最后,想象一个序列,你获得了一段时间的峰值,然后它平静下来。然后另一个峰值冲击了几秒钟,然后平静下来一两个小时,然后再次出现峰值。这种情况也会按预期工作,发送两次电子邮件,每次针对持续的峰值,而不是中间的一次迭代峰值(因为在这种情况下,repeated 永远不会超过 1)。

如果你仔细查看过脚本,你就会注意到,乍一看似乎是无害的“echo”语句实际上是在向管理员发送电子邮件消息。语句的后半部分正在进行繁重的工作


echo "Warning: DDOS in process? Current httpd count
    = $count" | sendmail $admin

我是老派,所以我直接使用 MTA(邮件传输代理),但实际上,更好的方法是这样做,这种方法会让电子邮件包含主题行,更像是这样


echo | mail -s "DDOS Warning" $admin

因为 sendmail 被设计为在后台使用,所以它缺乏像 -s 标志这样的细化功能来指定消息的主题。

事实上,你可以通过将整个命令变成一个变量来使它更加优雅


sendmsg="mail -s 'DDOS Warning' $admin"

然后你可以轻松地将其添加到 echo 语句的末尾


echo | $sendmsg

这种方法最大的问题是什么?如果出现任何错误,管道仅适用于 stdout,而不适用于 stderr,并且这些错误将会丢失。

如果你要每隔几分钟从 cron 运行脚本,你可以使用它通过电子邮件发送 stdout + stderr 的功能,但这将涉及更复杂的上下文跟踪解决方案(如前所述)。

好了,我要在我的服务器上安装这个脚本,看看它在一段时间内表现如何。你呢?亲爱的读者,你的任务是提出一些建议主题,供我在我的专栏中探讨。你希望看到创建哪些脚本,做什么用?

Dave Taylor 在 UNIX 和 Linux 系统上破解 shell 脚本已经很长时间了。他是 Learning Unix for Mac OS XWicked Cool Shell Scripts 的作者。你可以在 Twitter 上找到他 @DaveTaylor,你也可以通过他的技术问答网站联系他:Ask Dave Taylor

加载 Disqus 评论