案例分享

之前遇到一个十分精彩的应急响应案例,当时溯源和问题解决都很顺利。但是后续在复现这个现象以及渗透测试中攻击利用这个威胁点上出现了一些问题,断断续续研究了好些日子才几近分析完成。

先说一下这个应急案例,事情发生在当天下午,然而我是晚上约9点半的时候才接到电话。然后惯例建群,拉上项目组和各大领导们在群里说明事件严重性,要求全力支持配合,接着现场技术人员就把出问题的Apache服务器的access.log打开截了个图丢给我,然后告诉我大概现象就是两点:
1、 Apache服务器的access.log日志出现很多奇怪日志;
2、 该服务器性能下降,内存占用高。

拿到截图后,可以从这个access.log中看到很多奇怪的第三方域名,没有一个域名是属于本地合法域名集的,而且响应码几乎全部是200,少数的几个404确实也是对应的网站域名不可访问。

由于本人才疏学浅,没见过什么大世面,属于部门一等一的划水运动员。刚开始以为这就是一个简单的url跳转漏洞导致的奇怪的日志记录。对这些第三方域名进行访问测试,基本都是一些博彩类网站或者一些日常娱乐性站点,比如花椒直播,基本上可以认定该攻击者目的应该进行引流攻击,赚取广告流量费用,差不多可以作为黑产的一种类型了。接着我再看了看日志记录的源IP发现是一个内网IP地址,然后我就陷入了沉思。这个攻击者是傻么?既然他已经拿到一台内网服务器,为什么还要从别的内网服务器进行间接访问?这不是很费劲么?(基于后面的事实证明,不是他傻是我傻。捂脸哭。)

从我错误的分析来看,前后是矛盾的。因为目前可以确定的是该攻击者是在进行黑产交易,但是从以往对黑产类型的攻击事件的应急响应可以得到一个比较显著且具有实际意义的结论,就是黑产事件(例如勒索等)基本是利用的一些“著名”且具有“攻击成本低”的“高危便携性”的漏洞,例如struts2,这个攻击者不可能花费大量精力来内网漫游做这种广告流量的黑产交易,因为成本太高,兑现效率太低。

接着我比对了正常的access.log发现正常的access日志的Request请求并不会包含http协议头和域名字段,而是只记录url路径。这时候我才发现事情并不简单。

经过再一次地分析后发现这是一个利用中间件服务器的代理功能进行恶意代理攻击的事件,也就是说这台Apache服务器被人作为中间代理去访问其他站点。致使其在日志上记录下了代理日志,而这些代理日志就是在案例图中看到的那些第三方域名访问记录。

基本上原因是找到了,但是还遗留了一个问题,就是那个内网源IP是怎么回事?我想了一会,判断这可能是一台做反向代理的机器,而且很大概率上是可能是一台和Apache同处于DMZ区域的WAF。经过与客户的确认,确实如我想的一般。

这里对WAF的4种部署方式做个简单的概念叙述。
WAF一共有4种部署方式:
1、 串联部署
最为常见的部署方式,增加WAF负载,容易出现单点故障;
2、 旁路部署
需要进行二层或三层流量牵引,部署复杂,但是可以降低WAF负载,提高利用效率,只转发Web相关流量;
3、 反向代理
客户端与服务器端通讯不透明,对外IP即为WAF的公网IP,WAF接收到请求后将更换请求源地址为自身IP,向内网发起请求转发;
4、 镜像监听
只能检测服务是否被攻击, 不能对客业务进行防护;

也就是说该案例的实际网络架构和攻击者的攻击路径分析如下图所示:

画完这个图,我看着这个架构越看越觉得我好像漏了什么东西,这个图我越看越觉得好像曾经在哪见到过。翻了翻和客户的聊天记录,我果然漏了一个点。现象分析完成后我现在只能溯源得到为什么会出现奇怪的日志,但是我并不能通过这个代理攻击来澄清服务器性能下降,内存占用高的问题。而这个恶意代理攻击我或许在之前从未遇到,但是我遇到过并实际手动操作过另一个几乎是利用了同一个安全威胁点的攻击模式,CC(Challenge Collapsar)攻击。

CC攻击就是利用多台僵尸机进行多线程http代理向目标服务器发起大量的真实http请求,使其消耗掉大量的并发资源,拖慢整个网站,造成拒绝服务。但是在一般的CC攻击中,我们往往只把注视点放在被攻击方,而那些僵尸机其实也是面临同样的资源消耗问题,其内存性能也会随之受到侵扰。没做过CC攻击实际操作的人可能以为那些僵尸机真的就是需要一台一台入侵getshell才能用他们做CC攻击的代理服务器。实际上并不是如此,CC攻击的实际操作往往是在一些网络代理平台上批量获取可进行代理的服务器进行利用,一些著名的平台例如“快代理”,“花刺代理”,“西刺代理”等等,每天都在发布大量可用的代理服务器的IP和端口供用户使用。而CC攻击往往就是直接拖下这些代理IP端口,直接加载到CC攻击器的List模块,然后一键攻击即可(这里感谢实施部门的CC同学,当然他叫CC是因为他有一台大众CC,不过他也是CC攻击的一般性实操人)。

代理类型

正常导致这种恶意代理攻击威胁是因为服务器开启了正向代理开关,导致攻击者可以利用该服务器作为中间代理来访问其他站点。除了正向代理外,常见的还有反向代理,也就是刚刚所说的WAF的部署方式,还有一种透明代理。常见的就是这几种:正向代理,反向代理,透明代理,CONNECT通道。

缺陷修复

1、 最先肯定是关闭正向代理开关。
正确配置Apache的mod_proxy模块。在httpd.conf配置中将虚拟主机中配置修改为ProxyRequests Off,取消使用正向代理。
2、 在关闭正向代理后,会发现只能杜绝部分代理请求,还存在一些代理请求不能被屏蔽,接下来就通过.htaccess文件配置转发规则,以白名单加黑名单的双向措施在中间件层面来解决这个问题。

.htaccess文件内容如下

RewriteEngine on    //启用rewrite引擎
RewriteBase /       //设置Base地址,/为根目录

RewriteCond %{HTTP_HOST} example.com [NC]
//配置白名单域名,允许flag为NC,表示允许转发
RewriteRule ^(.*)$ http://www.example.com/$1 [NC] [L,R=301]
//配置url地址的正则表达式,配置flag为L,R=301,表示响应码配置为301跳转

RewriteCond %{HTTP_HOST} www.example.com [NC]
RewriteRule ^(.*)$ http://www.example.com/$1 [NC] [L,R=301]

RewriteRule ^(.*)$ - [F]
//配置正则表达式匹配所有(因为是按顺序匹配,这里即匹配非白名单的所有第三方非法域名),配置flag为F,表示403 Forbidden。

启用.htaccess,使其生效

  1. 在http.conf将如下配置的注解取消,启用mod_rewrite模块
    LoadModule rewrite_module modules/mod_rewrite.so
  2. 将AllowOverride None修改为AllowOverride All
# AllowOverride controls what directives may be placed in .htaccess files.
# It can be "All", "None", or any combination of the keywords:
# Options FileInfo AuthConfig Limit
配置AllowOverride None为AllowOverride All
C、改正vhost配置,给网站web目录添加一些Options配置,例如
<VirtualHost *:80>
ServerName sf2.aonola.com
DirectoryIndex app.php
DocumentRoot /www/sf2_aonola_com/web(根目录)

<Directory " /www/sf2_aonola_com/web">
# Options Indexes FollowSymLinks
# Options -Indexes IncludesNOEXEC -ExecCGI FollowSymLinks
Options FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

禁用不安全的HTTP方法,基本原则是只允许开启GET和POST方法。特别地,要禁用CONNECT方法。

重启服务器,策略生效

当然这样解决的操作基本上只能作为临时修复办法,因为通过这种方式是通过设置403 Forbidden来禁止转发第三方域名的请求,只能解决恶意代理攻击,但是无法有效解决服务器负载过高的问题,因为请求最终还是会发送到服务器上,服务器还是要对其做相关处理。

所以终极解决办法就是:
你有WAF,你为什么不用WAF呢?或者F5也行啊。

攻击测试

经过全方面的测试,有如下5种测试,现在对这5种方法做简单分析。其中主要是对nmap的nse脚本和msf的rb脚本做了代码分析,其实都是简单的socket代码,主要是想锻炼一下代码溯源的能力,以及理清两个渗透杀器nmap和msf的主体代码结构和调用体系。
1、 nmap的脚本测试(分析以Windows环境为例);
我们先分析一下http代理测试,该测试使用的脚本为http-open-proxy,该脚本位于nmap的scripts目录下,名称为http-open-proxy.nse

我们打开这个nse文件看一下代码里这个nmap脚本到底是怎么使用以及怎么实现测试的。

从简单的description中我们可以看到这个脚本就是用来测试HTTP代理是否打开,但是注意去看详细描述会发现,该脚本是以测试通过http代理是否能够访问google,响应码不限于200,301和302的形式来判断的。国内的朋友用起来就会有一些小麻烦了。不过仔细研读一下以下的代码,发现其实它不仅以google为测试url,同时作为测试url的还有wikipedia和computerhistory的官网。可惜,国内访问要不就是访问不了要不就是速度很慢。

但是nmap考虑到了这一点,在@usage中,我们看到他贴心地为我们加上了除了基本的nse使用方法,也就是—script http-open-proxy以外,在2009-05-14的版本更新中Joao Correa为我们添加了自定义proxy.url的参数方案。

所以在全局代码中我们看到了两个基本的function,一个是custom_test(),一个是default_test()。从参数获取我们可以看到custom_test()要求提供host,port,test_url,pattern,而default_test()要求提供host和port即可,完全符合usage中的描述。

由于基本测试形式一样,唯一不同点就是default_test()方法中固定了url和pattern参数为默认值。所以我们这里仅仅分析一下custom_test()的方法实现即可。

首先定义两个最后需要用来反馈给用户的return值,lstatus和response,初始化lstatus为false,最后只要存在http proxy打开,赋值为true。接着就是初始化各个入口参数,hostname,port,test_url之类的,这里还对test_url传入的用户输入值做了处理,如果用户输入没有加上http://,会自动为该参数加上协议头。然后开始执行程序主体,调用proxy的属性方法,分别用get代理请求方法、head代理测试方法和connect通道代理方法来进行测试,以repsonse的响应码判断是否有效。

由于三种方法的区别仅仅在于使用的HTTP方法不同,这里就仅以test_get()方法为示例进行分析。我们追溯一下test_get()方法,由于其是属于proxy的属性方法,进入proxy的定义中去找一下,定位到proxy.lua找到test_get()方法。

这边首先通过connectProxy()方法进行连接,我们通过定位到connectProxy()方法,发现其实就是简单的socket连接,并且这里的socket连接是通过proxyType的指定来进行不同类型的连接,回归到custom_test()发现三种测试方法都指定proxyType为“http”类型,所以这里就不用sock4和sock5的测试方法来进行测试。回过来看测试方法,也就是构造一个简单的request发送请求,读取器response的响应码即可判断。其实说到底就是建立一个带porxy参数的socket进程。

可以看到与它的名字相符,这只是一个http代理测试的脚本,同样既然这边定义了socks代理连接,那肯定有socks代理的测试脚本,没错,就是socks-open-proxy。

socks-open-proxy的基本方法也是和http-open-proxy一样,同样也是custom_test()方法和default_test()方法,所以也是可以自定义测试url的。跟之前分析的一样,唯一区别也就是在于proxyType的不同,这边是使用socks4和socks5两种类型。

所以使用nmap做测试的总结方法就是如下一条命令:
nmap 192.168.1.1 --script http-open-proxy socks-open-proxy --script-args proxy.url=www.baidu.com

2、 msfconsole的auxiliary/scanner/http/open_proxy模块
先看一下msf接口的简单description:HTTP Open Proxy Detection,基本与nmap的脚本功能出发点是一样的。

先调用一下这个模块看一下有哪些options是属于用户输入范畴的。

可以看到msf的模块也支持自定义checkurl,并且提供自定义测试标记,即响应码和匹配模式,并且还支持是否验证CONNECT通道代理方法。所以可以发现从用户自定义角度来说,msf相对nmap会更加人性化。不过基本上也是和nmap有异曲同工之妙,为什么这么说,因为,emmm……

然后对模块的方法实现进行一个分析,首先是一个初始化方法,其实就是默认值的赋值,和刚刚在options选项中看到的基本一样。

第二个方法是run_host(),这个方法就是自定义用户输入的内容。直接看一下执行主体的verify_target()方法,脚本里写的代码很简单。就是调用了send_request_cgi()方法获取到response,然后对response处理分析来判断是否存在proxy打开的情况。判断一共分3中,没有response判断没有proxy情况,判断普通http代理就通过判断响应码或者响应数据中的匹配模式,而判断CONNECT通道代理就是只判断响应码是否符合。

其实重点在于这个send_request_cgi()方法,定位一下这个方法地址,先看一下全局include,可以确定为include Msf::Exploit::Remote::HttpClient,找到这个方法的实现脚本地址为/opt/metasploit-framework/embedded/framework/lib/msf/core/exploit/http/client.rb,其实如果看不懂的话可以用grep一键找。

理一下send_request_cgi()方法主体,执行方法主要是下面这一段,所以接下来要定位一下connect()方法,来弄清楚request_cgi的具体执行参数的表达含义。

connect()方法首先会判断一下ssl协议,然后开始连接http server,可以看到这边的参数opt其实就是制定远程主机rhost的值,实际上就是check_url传入的值。

追踪到/opt/metasploit-framework/embedded/framework/lib/rex/proto/http/client.rb的request_cgi()方法,其实也就是基本的定义参数,然后进行client客户端去连接。

这个client.rb的整体的代码结构和之前的有点类似,只不过更加细化,在initialize()方法中可以看到读取了connect()方法中传入的8个参数值。这边其实分析到底也是一层socket连接,赋予其proxy的参数。所以理论上都是和nmap是类似的。

3、 花刺代理工具测试;
工具是傻瓜工具,一看就知道怎么用,不再赘述,值得说的是该工具需使用管理员身份打开。

4、 在线代理有效性测试;
经过一些测试,该测试站点测试效果很好。
http://web.chacuo.net/netproxycheck

5、上线前白盒分析(基线审核)httpd.conf相关安全配置文件

源链接

Hacking more

...