如需转载,请标明原文出处以及作者

陈锐 RuiChen @kiwik

2017/11/3 19:23:06


写在最前面:

网络安全的东西距离我现在的工作比较遥远,说实话没有想到会被攻击,但还是遇上了,也许攻击者并非具有针对性,让我有一天的时间将漏洞补上,算是侥幸。在公有云上使用公有 IP 暴露 HTTP 服务,还是要多加小心。

背景

这段时间在做 OpenLab 相关的工作,目标是构建一套 CI 系统,将与 OpenStack 相关的 SDK 项目(OpenStack社区之外的)与 OpenStack 集成并进行自动化测试,从而发现 SDK 的问题,帮助 SDK 修复改进,促进 OpenStack 北向 API 生态发展。我们选用的是与 OpenStack 社区一致的方案,使用 Zuul v3 + Nodepool,配合 Github 的 Pull Request 作为触发条件,自动触发测试任务,并使用华为公有云自动发放虚拟机作为测试环境,感兴趣的可以在此了解。

我们提供了一个测试任务的状态页面,实时展示任务执行情况,类似于 OpenStack 社区的 zuulv3.openstack.org ,它由 Apache 提供服务,运行在公有云虚拟机上,虚拟机绑定了公网 IP ,对外可以访问。

Apache 异常

由于我们的 HTTP 服务直接监听 80 端口,所以公网上可以直接通过 IP 地址访问。有一天同事反映页面打开速度变得很慢,不像前两天流畅,感觉不对劲儿,登录看了一下 Apache 的 access 日志有好几个G,用 tail 跟了一下日志文件,发现每秒都有十几条请求,日志刷屏瞬间搞得我头很晕。分析了一下 access 日志的内容,其中 99.9% 都是访问一些国外网站的奇怪的请求,返回码都是 404 。重启 Apache 之后,瞬间又有大量的请求进入。没想到这么偏远的角落都有人来,看起来是要摆上几个防御塔了 :)

mod_evasive

Apache 有一个 evasive 模块可以帮助识别并阻止 DoS/DDos 攻击,原理是跟踪 HTTP 请求,如果同一请求在一段配置的时间内访问超过阈值,就报警,然后在应用级别阻止后续的请求,返回 403 Forbidden。配置文件 /etc/apache2/mods-enabled/evasive.conf 可以设置具体的识别阈值和识别窗口大小。mod_evasive 的安装启用方式如下:

sudo apt-get install libapache2-mod-evasive
sudo mkdir /var/log/mod_evasive
sudo a2enmod evasive
sudo systemctl reload apache2 

下面就是系统日志中的一条报警:

Nov 02 06:59:23 openlab-cicd-misc mod_evasive[32968]: Blacklisting address 54.36.188.35: possible DoS attack.

mod_security

mod_security 相比 mod_evasive 要更强大一些,作为一个 Web 应用防火墙(WAF),它使用规则引擎识别多种网络攻击行为模式,例如:SQL 注入,跨站攻击等,并可以改变 Request 和 Response 中的内容做出防御,它也工作在应用级别。安装和启用 mod_security 的方式如下:

sudo apt-get install libapache2-modsecurity
sudo mv /etc/modsecurity/modsecurity.conf{-recommended,}
sudo a2enmod security2
sudo systemctl reload apache2

mod_security 默认是启用在侦测模式下 SecRuleEngine DetectionOnly,只发现问题,不解决问题,球队中的“眼神防守者”,我们需要在配置文件 /etc/modsecurity/modsecurity.conf 中改为 SecRuleEngine On,使它在发现攻击行为后作出相应的防守动作, mod_security 功能强大配置也比较多,在这我就不过多叙述。

启用 mod_security 之后,默认在 Apache2 日志目录下会生成审计日志 /var/log/apache2/modsec_audit.log,这个日志和其他的 Apache 日志都是 fail2ban 日志数据源。内容如下:

--d0f2d400-A--
[02/Nov/2017:09:31:54 +0000] WfrmCsCoAMcAAS3qeMUAAAAJ 118.68.247.170 55708 192.168.0.199 80
--d0f2d400-B--
CONNECT 45.33.54.195:80 HTTP/1.0

--d0f2d400-F--
HTTP/1.1 405 Method Not Allowed
Allow: GET,HEAD,POST,OPTIONS
Content-Length: 313
Connection: close
Content-Type: text/html; charset=iso-8859-1

--d0f2d400-E--
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>405 Method Not Allowed</title>
</head><body>
<h1>Method Not Allowed</h1>
<p>The requested method CONNECT is not allowed for the URL /index.html.</p>
<hr>
<address>Apache/2.4.18 (Ubuntu) Server at 45.33.54.195 Port 80</address>
</body></html>

--d0f2d400-H--
Stopwatch: 1509615114235762 483 (- - -)
Stopwatch2: 1509615114235762 483; combined=19, p1=5, p2=10, p3=1, p4=0, p5=3, sr=0, sw=0, l=0, gc=0
Response-Body-Transformed: Dechunked
Producer: ModSecurity for Apache/2.9.0 (http://www.modsecurity.org/).
Server: Apache/2.4.18 (Ubuntu)
Engine-Mode: "ENABLED"

--d0f2d400-Z--

fail2ban

fail2ban 与上面两个有本质的不同,它是一个独立软件,并不是 Apache 的扩展模块,更重要的是它工作在网络级别,上面两个工作在应用级别。fail2ban 通过扫描日志文件,可以是 Apache 也可以是 sshd 或其它应用的日志,发现符合模式的日志内容之后,阻止具有恶意迹象的 IP 继续对系统的访问。安装和启用 fail2ban 的方式如下:

sudo apt-get install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo systemctl restart fail2ban
sudo fail2ban-client status

fail2ban 默认只启用了对于 sshd 防护,需要修改配置文件 /etc/fail2ban/jail.local 启用其他的能力,下面就是我配置的一段通过分析 apache mod_security 的审计日志,启动防御的配置段:

[apache-modsecurity]
enabled = true
port     = http,https
logpath = /var/log/apache2/modsec_audit.log
maxretry = 2

fail2ban 内置了识别很多种网络攻击的 filter 文件,在 /etc/fail2ban/filter.d/ 中,我对 apache-modsecurity.conf 稍微修改了一下,满足我的要求。其中最重要的就是参数 failregex,通过正则表达式抓取日志中的攻击方 IP,然后进行阻止。

[INCLUDES]
before = apache-common.conf

[Definition]
#failregex = ^%(_apache_error_client)s ModSecurity:  (\[.*?\] )*Access denied with code [45]\d\d.*$
failregex = \[.*?\]\s[\w-]*\s<HOST>\s

ignoreregex = 

认真学习过正则表达式,让我一直觉得受益匪浅。

我一开始并不太理解 mod_security 和 mod_evasive 所谓应用级别的阻止与 fail2ban 网络级别的阻止有什么不同,对比启用 fail2ban 之后的现象发现,启用了 mod_security 和 mod_evasive 之后,网络请求还是会继续进入 Apache 处理,access 日志中还是存在大量异常的请求,只不过响应码从 404 变成了其他 403 之类,日志还是会暴涨好几个G,但是启用了 fail2ban 之后,进入 Apache 的请求明显变少了,看了下 fail2ban 的文档,发现一个老朋友 iptables,原来 fail2ban 在获取到恶意 IP 之后,直接通过 iptables 规则,将网络请求 reject 掉了,请求压根就不会到应用层级处理,当然就不会轮到 Apache 处理,了然,就如 OpenStack 中的安全组。

# iptables -L -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
f2b-apache-shellshock  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-modsecurity  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-fakegooglebot  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-botsearch  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-nohome  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-overflows  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-noscript  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-badbots  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-apache-auth  tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 80,443
f2b-sshd   tcp  --  0.0.0.0/0            0.0.0.0/0            multiport dports 22
DROP       all  --  93.188.246.105       0.0.0.0/0           

Chain f2b-apache-auth (1 references)
target     prot opt source               destination         
REJECT     all  --  54.36.188.35         0.0.0.0/0            reject-with icmp-port-unreachable
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           

Chain f2b-apache-modsecurity (1 references)
target     prot opt source               destination         
REJECT     all  --  114.37.234.98        0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  106.11.157.172       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  106.11.157.177       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  206.16.132.62        0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  106.11.155.162       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  106.11.156.176       0.0.0.0/0            reject-with icmp-port-unreachable
REJECT     all  --  106.11.158.190       0.0.0.0/0            reject-with icmp-port-unreachable


Published

03 November 2017

Category

OpenStack

Tags