Normal view

There are new articles available, click to refresh the page.
Before yesterdayZero Day Initiative - Blog

Finding Deserialization Bugs in the SolarWinds Platform

21 September 2023 at 16:12

It’s been a while since I have written a blog post, please accept my sincerest apologies. This is because a lot of fun stuff that I’ve recently done is going to be presented during conferences.

Please treat this post as a small introduction to my upcoming Hexacon 2023 talk titled “Exploiting Hardened .NET Deserialization: New Exploitation Ideas and Abuse of Insecure Serialization”. The entire talk and research was inspired by two small research projects, one of which focused on issues in SolarWinds deserialization.

In this blog post, I would like to present four old vulnerabilities that were fixed within the last year:

CVE-2022-38108
CVE-2022-36957
CVE-2022-36958
CVE-2022-36964

A small part of the Hexacon talk will show how I have bypassed patches to some of these vulnerabilities. Right now, we will focus on the original issues.

CVE-2022-38108

This vulnerability was already mentioned in this blog post. Let me reintroduce it to you in more detail.

Several SolarWinds services communicate with each other through a RabbitMQ instance, which is accessible through port 5671/TCP. Credentials are required to access it. However:

— High-privileged users were able to extract those credentials through SolarWinds Orion Platform.
— I later found CVE-2023-33225, which allowed low-privileged users to extract those credentials.

This vulnerability targeted the SolarWinds Information Service. In order to deliver an AMQP message to the Information Service, the Routing-Key of the message must be set to SwisPubSub.

Figure 1 - Routing-Key in AMQP message

Now, let’s verify how SolarWinds handles those messages! We can start with the EasyNetQ.Consumer.HandleBasicDeliver method:

At [1], the code retrieves the properties of the AMQP message. Those properties are controlled by the attacker who sends the message.

At [2], it creates an execution context, containing both the AMQP message properties and the message body.

At [3], it executes a task to consume the message.

This leads us to the Consume method:

At [1], EasyNetQ.DefaultMessageSerializationStrategy.DeserializeMessage is called. It accepts the message properties and the message body as input. The interesting stuff happens here.

At [1], we can see something really intriguing. A method named DeSerialize is called and it returns an output of type Type. As an input, it accepts the Type property from the message. That’s right – we can control messageType type through an AMQP message property!

At [2], it calls BytesToMessage, which accepts both the attacker-controlled type and the message body as input.

At [1], the message body is decoded as a UTF-8 string. It is expected to contain JSON-formatted data.

At [2], the deserialization is performed. We control both the target type and the serialized payload.

At [3], it can be seen that the TypeNameHandling deserialization setting is set to Auto.

We have more than we need to achieve remote code execution here! To do that, we have to send an AMQP message with the Type property set to a dangerous type.

Figure 2 - Deserialization Type control through AMQP properties

In the message body, we must deliver the corresponding JSON.NET gadget. I have used a simple WindowsPrincipal gadget from ysoserial.net, which is a bridge for the internally stored BinaryFormatter gadget. Upon the JSON deserialization, the RCE will be achieved through the underlying BinaryFormatter deserialization.

RCE achieved!

CVE-2022-36957

In the previous vulnerability, we were able to fully control the target deserialization type through the AMQP property. When I find such a vulnerability, I like to ask myself the following question: “What does a legitimate message look like?” I often check the types that are being deserialized during typical product operation. It sometimes leads to interesting findings.

I quickly realized that SolarWinds sends messages of one type only:

         SolarWinds.MessageBus.Models.Indication

Let’s take a moment to analyze this type:

At [1] and [2], we can see two public members of type SolarWinds.MessageBus.Models.PropertyBag. The fun begins here.

At [1], you can see the definition of the class in question, SolarWinds.MessageBus.Models.PropertyBag.

At [2], a custom converter is registered for this class - SolarWinds.MessageBus.Models. PropertyBagJsonConverter. It implements the ReadJson method, which will be called during deserialization.

At [1], the code iterates over the JSON properties.

At [2], a JSON value is retrieved and casted to the JObject type.

At [3], a Type is retrieved on the basis of the value stored in the t key.

At [4], the object stored in the v key is deserialized, where we control the target deserialization type (again)!

You can see that we are again able to control the deserialization type! This type is delivered through the t JSON key and the serialized payload is delivered through the v key.

Let’s have a look at a fragment of a legitimate message:

We can take any property, for instance: IndicationId. Then, we need to:
• Set the value of the t key to the name of a malicious type.
• Put a malicious serialized payload in the value of the v key.

As the JSON deserialization settings are set to TypeNameHandling.Auto, it is enough to deliver something like this:

Now, let’s imagine that the first bug described above, CVE-2022-38108, got fixed by hardcoding of the target deserialization type to SolarWinds.MessageBus.Models.Indication. After all, this is the only legitimate type to be deserialized. That fix would not be enough, because SolarWinds.MessageBus.Models.Indication can be used to deliver an inner object, with an attacker-controlled type. We have a second RCE through control of the type here.

CVE-2022-36958

SolarWinds defines some inner methods/operations called “SWIS verbs”. Those verbs can be either:
a) Invoked directly through the API.
b) Invoked indirectly through the Orion Platform Web UI (Orion Platform invokes verbs internally).

There are several things that we need to know about SWIS verbs:
• They are invoked using a payload within an XML structure.
• They accept arguments of predefined types.

For instance, consider the Orion.AgentManagement.Agent.Deploy verb. It accepts 12 arguments. The following screenshot presents those arguments and their corresponding types.

Figure 3 - Arguments for Orion.AgentManagement.Agent.Deploy

The handling of arguments is performed by the method SolarWinds.InformationService.Verb. VerbExecutorContext.UnpackageParameters(XmlElement[], Stream):

At [1], the Type is retrieved for the given verb argument.

At [2], a DataContractSerializer is initialized with the retrieved argument type.

At [3] and [4], the argument is deserialized.

We know that we are dealing with a DataContractSerializer. We cannot control the deserialization types though. My first thought was: I had already found some abusable PropertyBag classes. Maybe there are more to be found here?

It quickly turned out to be a good direction. There are multiple SWIS verbs that accept arguments of a type named SolarWinds.InformationService.Addons.PropertyBag. We can provide arbitrary XML to be deserialized to an object of this type. Let’s investigate!

At [1], the ReadXml method is defined. It will be called during deserialization.

At [2], the code iterates over the provided items.

At [3], the key element is retrieved. If present, the code continues.

At [4], the value of the type element is retrieved. One may safely assume where it leads.

At [5], the value element is retrieved.

At [6], the Deserialize method is called, and the data contained in both the value and type tags are provided as input.

At [7], the serialized payload and type name are passed to the SolarWinds.InformationService.Serialization.SerializationHelper.Deserialize method.

Again, both the type and the serialized payload are controlled by the attacker. Let’s check this deserialization method.

At [1], the code checks if the provided type is cached.

If not, the type is retrieved from a string at [2].

At [3], the static DeserializeFromStrippedXml is called.

As you can see, the static DeserializeFromStrippedXml method retrieves a serializer object by calling SerializationHelper.serializerCache.GetSerializer(type). Then, it calls the (non-static) DeserializeFromStrippedXml(string) method on the retrieved serializer object.

Let’s see how the serializer is retrieved.

At [1], the code tries to retrieve the serializer from a cache. In case of a cache miss, it retrieves the serializer by calling GetSerializerInternal ([2]), so our investigation continues with GetSerializerInternal.

At [3], an XmlTypeMapping is retrieved on the basis of the attacker-controlled type. It does not implement any security measures. It is only used to retrieve some basic information about the given type.

At [4], an XmlStrippedSerializer object is initialized. Four arguments are supplied to the constructor:
• A new XmlSerializer instance, where the type of the serializer is controlled by the attacker(!).
• The XsdElementName of the target type, obtained from the XmlTypeMapping.
• The Namespace of the type, also obtained from the XmlTypeMapping.
• The type itself.

So far, we have two crucial facts:
• We are switching deserializers. The overall SWIS verb payload and arguments are deserialized with a DataContractSerializer. However, our PropertyBag object will eventually be deserialized with an XmlSerializer.
• We fully control the type provided to the XmlSerializer constructor, which is a key condition for exploitation.

It seems that we have it, another RCE through type control in deserialization. As XmlSerializer can be abused through the ObjectDataProvider, we can set the target deserialization type to the following:

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=b77a5c561934e08

However, let’s analyze the XmlStrippedSerializer.DeserializeFromStrippedXml(String) before celebrating.

Something unusual is happening here. At [1], a new XML string is being created. It has the following structure:

         <XsdElementName xmlns=’Namespace’>ATTACKER-XML</XsdElementName>

To sum up:
• The attacker’s XML gets wrapped with a tag derived from the delivered type (see GetSerializerInternal method).
• Moreover, the retrieved Namespace is inserted into the xmlns attribute.

The attacker controls a major fragment of the final XML and controls the type. However, due to the custom XML wrapping, the ysoserial.net gadget will not work out of the box. The generated gadget looks like this:

The first tag is equal to ExpandedWrapperOfLosFormatterObjectDataProvider. This tag will be automatically generated by the DeserializeFromStrippedXml method, thus we need to remove it from the generated payload! When we do so, the following XML will be passed to the XmlSerializer.Deserialize method:

We still have a major issue here. Can you spot it?

When you compare both the original ysoserial.net gadget and our current gadget, one big difference can be spotted:
• The original gadget defines two namespaces in the root tag: xsi and xsd.
• The current gadget contains an empty xmlns attribute only.

The ObjectInstance tag relies on the xsi namespace. Consequently, deserialization will fail.

Luckily, the namespace does not have to be defined in the root tag specifically. Accordingly, we can fix our gadget by defining both namespaces in the ProjectedProperty0 tag. The final gadget is as follows:

In this way, we get a third RCE, where we fully control the target deserialization type!

Here is a fragment of the API request, where the malicious SWIS verb argument is defined:

CVE-2022-36964

Technically, this issue is identical to CVE-2022-36958. However, it exists in a different class that shares the same implementation of the ReadXml method. In this case, the vulnerable class is SolarWinds.InformationService.Contract2.PropertyBag.

An argument of this type is accepted by the TestAlertingAction SWIS verb, thus this issue is exploitable through the API.

This class may appear familiar to some of you. I already abused that same class with JSON.NET deserialization in CVE-2021-31474. Almost one and a half years later, I realized that this class can be abused in a totally different way as well.

Summary

In this blog post, I have shown you four different deserialization vulnerabilities in SolarWinds where the attacker could control the type of the deserialized object. One of them was particularly interesting, because DataContractSerializer could be used to ultimately reach XmlSerializer. During my Hexacon 2023 talk, I will show you some of the patches applied to the described issues and I will show you how I have bypassed them by using custom deserialization gadgets. These patch bypasses have also been patched by SolarWinds, but the discussion will show how hunting deserialization bugs can lead to some fun discoveries.

I hope you liked this writeup. Until my next post, you can follow me @chudypb and follow the team on Twitter, Mastodon, LinkedIn, or Instagram for the latest in exploit techniques and security patches.

Unpatched Powerful SSRF in Exchange OWA – Getting Response Through Attachments

2 November 2023 at 16:18

Server Side Request Forgery (SSRF). This vulnerability class triggers a wide range of emotions and reactions, ranging from complete ignorance to panic. Though it is included in the OWASP Top 10 list of web application security risks, at times vendors tend to downplay it and not treat it seriously.

As usual, the truth lies somewhere in between. What appears to be SSRF may sometimes in fact be intended functionality. Even so, an attacker may be able to abuse that functionality to improperly disclose sensitive information, either from the application containing the SSRF or from unrelated internal systems.

You may have noticed battles between researchers and vendors where an SSRF vulnerability was at the center of the disagreement. The fun part starts when a single vendor responds to different SSRF vulnerabilities in different ways and you are not able to reverse engineer their decision-making algorithm.

Whenever I find an SSRF vulnerability, my personal approach is to divide the assessment into four main categories. 

a)     Internal vs. external application

Ideally, we want to find SSRF vulnerabilities in applications that are widely exposed to the internet. Such a vulnerability may allow you to interact with an internal network to which you would not have direct access.

This isn’t to say that SSRF vulnerabilities in internal applications are without value. Sometimes, internal applications may be placed in the DMZ or another restricted area of the network. When you are an internal attacker (an attacker who is already in the internal network), you may find an SSRF useful for reaching some inaccessible networks or machines. Still, it’s typically a much less attractive case than an internet-reachable SSRF.

 b)     Does the product expose services on the loopback interface?

Some products expose services that are reachable only through a loopback interface. If we can interact with such a service through the SSRF, it allows us to extend the attack surface and to potentially chain it with other vulnerabilities. It makes the SSRF potentially more interesting and useful.

c)     Privileges required

As always, the strongest vulnerabilities are those that do not require authentication.

After that come vulnerabilities requiring low privileges. Though less powerful, they can still be very dangerous. Consider an externally exposed application with 50,000 users. An authenticated SSRF in such a case may be very dangerous, as we all know that threat actors have multiple ways of obtaining credentials.

Last and least, there are SSRFs that require administrative privileges. Typically, I almost immediately drop those. Seeing a vendor that fixes admin-level SSRF is a rarity.

d)     What can we achieve with the SSRF

This is a crucial category, and we can divide it into two subparts.

1 - Request shaping:

— What protocols can we use? HTTP, HTTPS, FTP, any other?
— How much do we control the request? In case of HTTP:

o   Can we pick the HTTP method? GET, POST or any other? If not, which method is being used for the request?
o   Can we fully control the URL? Are some query string parameters hardcoded, or do we have full control?
o   Can we specify arbitrary HTTP headers?
o   Can we insert arbitrary data into the request body?
o   And so forth.

2 - Response handling:

— Does the SSRF sink follow redirects? If so, does it allow switching protocols?
— Does it return a response to the attacker?

I find this last question particularly important. If the attacker can use the SSRF to receive a response, the information leak risk is real. I’ve done hundreds of penetration tests and believe me – SSRF that allows you to make GET requests to the internal network and receive the response will give you a ton of sensitive information.

That was long, I’m sorry. But this shows that the evaluation of SSRF vulnerabilities is in fact complex and it depends on many factors. When you find an SSRF vulnerability, you will need to do a similar evaluation by yourself. Afterward, you might find that when the vendor performs their own evaluation, they may have a completely different definition of a “potentially harmful SSRF”.

To illustrate, I once reported an SSRF vulnerability in Microsoft SharePoint, CVE-2023-28288. This vulnerability could be exploited by any authenticated user and allowed the attacker to make any HTTP GET request but did not return the response. Microsoft treated this vulnerability seriously and provided a quick fix. MSRC even assigned a higher CVSS score than we originally had. This may be because the vulnerability additionally allowed disclosure of the contents of local files with the xsl extension. I did not find that especially interesting, but perhaps Microsoft did.

On the other hand, the researcher known as Frycos found a pre-auth SSRF in Skype for Business, and more than a year passed waiting for the fix. It was not treated as an immediate threat. Quoting from that blog post:

Since the MSRC rejected my submission for this vulnerability with a “not meeting the bar” argument, I told them to publish a blog post instead.

I am doing something similar right now. Not so long ago, I had 4 hours to spare, and I decided to look at Exchange OWA. I quickly identified 3 SSRF issues, one of which seemed to be particularly dangerous:

— Exchange OWA is frequently exposed to the internet.
— The vulnerability could be exploited by any authenticated user (any user with a mailbox). Even though this means that authentication is required, the number of mailboxes deployed in some organizations can run into the hundreds of thousands.
— It allows performing any HTTP GET request, with full control over the URL and query string parameters.
It retrieves the content of the response.

As the attacker can abuse this SSRF to retrieve the content of the response, I thought it was a good finding. However, Microsoft did not agree:

MSRC has investigated this issue and concluded that this does not require immediate servicing. We have shared your report with the team responsible for maintaining the product or service and they will consider a potential future fix, taking the appropriate action as needed to help keep customers protected.

We do not have a timeline for when this review will occur, and will not be able to provide status for this issue moving forward.

In short: this may get fixed or it may not. If they decide to fix it, the patch may appear in 1 year or in 3 years. In general, we know nothing.

Accordingly, we informed Microsoft of our intention to publish this vulnerability as a 0-day advisory and a blog post. As we consider this issue potentially dangerous, we want organizations to be aware of the threat. For this reason, we are providing a PoC HTTP Request to be used for filtering and/or monitoring.

ZDI-CAN-22101 – CreateAttachmentFromURI Server-Side Request Forgery

When a user wants to attach a file to a message through Exchange OWA, he can use the “clip” button. It allows the selection of any file from the local file system.

Figure 1 — Inserting an attachment through the GUI

Quite an obvious thing, right? On the other hand, when I was going through the methods defined in the Exchange OWAService, I found an interesting one called CreateAttachmentFromUri.

At [1], the CreateAttachmentFromUri is initialized and then its Execute method is called.

At [1], the Uri object is instantiated on the basis of the attacker-controlled string.

The Execute method will finally lead us to CreateAttachmentFromUri.InternalExecute:

At [1], CreateAttachmentFromUri.DownloadAndAttachFileFromUri is called. The name of the method says everything that we need to know.

It leads to an asynchronous task. I am including a fragment of this task:

At [1], an HttpClient is created.

At [2], a request is made.

At [3], an attachment is created on the basis of the retrieved response.

According to that, this method allows performing an HTTP GET SSRF. The attacker can target any endpoint and can specify any query string parameters.

One may notice that this SSRF has an additional feature that makes it even more dangerous. It creates the attachment on the basis of the response. There are also bonus points for the fact that this SSRF sink handles redirects – they are supported by default by HttpClient, and the AllowAutoRedirect property is not modified by the code.

My guess is that this is some obsolete Exchange OWA feature, which has been deprecated and removed from the GUI. However, it has not been removed from the server-side code. This is only a guess though, as I have never been a serious user of OWA.

To sum up, the following attack scenario is possible:
• The attacker authenticates to OWA.
• The attacker creates a new draft message.
• The attacker invokes CreateAttachmentFromUri, triggering the SSRF.
• The response of the SSRF gets added to the mail message as an attachment.
• The attacker downloads the attachment and retrieves the response content.

I implemented this attack scenario in my exploit. The following screenshot presents a sample exploitation, where the http://internaltomcat.zdi.local:8080 URL was targeted.

Figure 2 — SSRF Exploit – retrieving the response from internal Tomcat server

The response content also can be retrieved easily through the GUI. You need to access your message and open the attachment.

Figure 3 — SSRF response stored in the attachment

Proof of Concept

In general, this SSRF can be exploited with a single HTTP Request to the OWA service:

Such a request produces SSRF. It will not allow you to retrieve the response content, though. If you want to retrieve the content, you must provide a valid message’s ChangeKey and Id in the respective JSON keys (here, they were set to poc). If a proper ChangeKey and Id are provided, the response will be attached to the specified message.

The JSON payload can be also delivered through the X-Owa-Urlpostdata HTTP header. The following snippet presents an example of such a request:

The following video presents this vulnerability in action.

Summary

Assessment of Server Side Request Forgery issues may be hard and lead to disagreements. Such vulnerabilities often do not impact the same product in which they exist, which is an argument frequently heard from vendors. However, they can be used as an access path for external attackers to reach the restricted areas of networks, thereby causing an impact on other systems in the corporate environment. Accordingly, I would recommend that vendors take SSRF seriously, and reassess functionality that involves forwarding arbitrary requests.

I hope you liked this writeup. Until my next post, you can follow me @chudypb and follow the team on Twitter, Mastodon, LinkedIn, or Instagram for the latest in exploit techniques and security patches.

 

Yesterday — 30 May 2024Zero Day Initiative - Blog

CVE-2024-30043: Abusing URL Parsing Confusion to Exploit XXE on SharePoint Server and Cloud

Yes, the title is right. This blog covers an XML eXternal Entity (XXE) injection vulnerability that I found in SharePoint. The bug was recently patched by Microsoft. In general, XXE vulnerabilities are not very exciting in terms of discovery and related technical aspects. They may sometimes be fun to exploit and exfiltrate data (or do other nasty things) in real environments, but in the vulnerability research world, you typically find them, report them, and forget about them.

So why am I writing a blog post about an XXE? I have two reasons:

·       It affects SharePoint, both on-prem and cloud instances, which is a nice target. This vulnerability can be exploited by a low-privileged user.
·       This is one of the craziest XXEs that I have ever seen (and found), both in terms of vulnerability discovery and the method of triggering. When we talk about overall exploitation and impact, this Pwn2Own win by Chris Anastasio and Steven Seeley is still my favorite.

The vulnerability is known as CVE-2024-30043, and, as one would expect with an XXE, it allows you to:

·       Read files with SharePoint Farm Service account permission.
·       Perform Server-side request forgery (SSRF) attacks.
·       Perform NTLM Relaying.
·       Achieve any other side effects to which XXE may lead.

Let us go straight to the details.

BaseXmlDataSource DataSource

Microsoft.SharePoint.WebControls.BaseXmlDataSource is an abstract base class, inheriting from DataSource, for data source objects that can be added to a SharePoint Page. DataSource can be included in a SharePoint page, in order to retrieve data (in a way specific to a particular DataSource). When a BaseXmlDataSource is present on a page, its Execute method will be called at some point during page rendering:

At [1], you can see the Execute method, which accepts a string called request. We fully control this string, and it should be a URL (or a path) pointing to an XML file. Later, I will refer to this string as DataFile.

At this point, we can derive this method into two main parts: XML fetching and XML parsing.

       a) XML Fetching

At [2], this.FetchData is called and our URL is passed as an input argument. BaseXmlDataSource does not implement this method (it’s an abstract class).

FetchData is implemented in three classes that extend our abstract class:
SoapDataSource - performs HTTP SOAP request and retrieves a response (XML).
XmlUrlDataSource - performs a customizable HTTP request and retrieves a response (XML).
SPXmlDataSource - retrieves an existing specified file on the SharePoint site.

We will revisit those classes later.

       b) XML Parsing

At [3], the xmlReaderSettings.DtdProcessing member is set to DtdProcessing.Prohibit, which should disable the processing of DTDs.

At [4] and [5], the xmlTextReader.XmlResolver is set to a freshly created XmlSecureResolver. The request string, which we fully control, is passed as the securityUrl parameter when creating the XmlSecureResolver

At [6], the code creates a new instance of XmlReader.

Finally, it reads the contents of the XML using a while-do loop at [7].

At first glance, this parsing routine seems correct. The document type definition (DTD) processing of our XmlReaderSettings instance is set to Prohibit, which should block all DTD processing. On the other hand, we have the XmlResolver set to XmlSecureResolver.

From my experience, it is very rare to see .NET code, where:
• DTDs are blocked through XmlReaderSettings.
• Some XmlResolver is still defined.

I decided to play around and sent in a general entity-based payload at some test code I wrote similar to the code shown above (I only replaced XmlSecureResolver with XmlUrlResolver for testing purposes):

As expected, no HTTP request was performed, and a DTD processing exception was thrown. What about this payload?

It was a massive surprise to me, but the HTTP request was performed! According to that, it seems that when you have .NET code where:
XmlReader is used with XmlTextReader and XmlReaderSettings.
XmlReaderSettings.DtdProcessing is set to Prohibit.
• An XmlTextReader.XmlResolver is set.

The resolver will first try to handle the parameter entities, and only afterwards will perform the DTD prohibition check! An exception will be thrown in the end, but it still allows you to exploit the Out-of-Band XXE and potentially exfiltrate data (using, for example, an HTTP channel).

The XXE is there, but we have to solve two mysteries:

• How can we properly fetch the XML payload in SharePoint?
• What’s the deal with this XmlSecureResolver?

XML Fetching and XmlSecureResolver

As I have already mentioned, there are 3 classes that extend our vulnerable BaseXmlDataSource. Their FetchData method is used to retrieve the XML content based on our URL. Then, this XML will be parsed with the vulnerable XML parsing code.

Let’s summarize those 3 classes:

       a) XmlUrlDataSource

       • Accepts URLs with a protocol set to either http or https.
       • Performs an HTTP request to fetch the XML content. This request is customizable. For example, we can select which HTTP method we want to use.
       • Some SSRF protections are implemented. This class won’t allow you to make HTTP requests to local addresses such as 127.0.0.1 or 192.168.1.10. Still, you can use it freely to reach external IP address space.

       b) SoapDataSource

       • Almost identical to the first one, although it allows you to perform SOAP requests only (body must contain valid XML, plus additional restrictions).
       • The same SSRF protections exist as in XmlUrlDataSource.

       c) SPXmlDataSource

       • Allows retrieval of the contents of SharePoint pages or documents. If you have a file test.xml uploaded to the sample site, you can provide a URL as follows: /sites/sample/test.xml.

At this point, those HTTP-based classes look like a great match. We can:
• Create an HTTP server.
• Fetch malicious XML from our server.
• Trigger XXE and potentially read files from SharePoint server.

Let’s test this. I’m creating an XmlUrlDataSource, and I want it to fetch the XML from this URL:

       http://attacker.com/poc.xml

poc.xml contains the following payload:

The plan is simple. I want to test the XXE by executing an HTTP request to the localhost (SSRF).

We must also remember that whatever URL that we specify as our source also becomes the securityUrl of the XmlSecureResolver. Accordingly, this is what will be executed:

Figure 1 XmlSecureResolver initialization

Who cares anyway? YOLO and let’s move along with the exploitation. Unfortunately, this is the exception that appears when we try to execute this attack:

Figure 2 Exception thrown during XXE->SSRF

It seems that “Secure” in XmlSecureResolver stands for something. In general, it is a wrapper around various resolvers, which allows you to apply some resource fetching restrictions. Here is a fragment of the Microsoft documentation:

“Helps to secure another implementation of XmlResolver by wrapping the XmlResolver object and restricting the resources that the underlying XmlResolver has access to.”

In general, it is based on Microsoft Code Access Security. Depending on the provided URL, it creates some resource access rules. Let’s see a simplified example for the http://attacker.com/test.xml:

Figure 3 Simplified sample restrictions applied by XmlSecureResolver

In short, it creates restrictions based on protocol, hostname, and a couple of different things (like an optional port, which is not applicable to all protocols). If we fetch our XML from http://attacker.com, we won’t be able to make a request to http://localhost because the host does not match.

The same goes for the protocol. If we fetch XML from the attacker’s HTTP server, we won’t be able to access local files with XXE, because neither the protocol (http:// versus file://) nor the host match as required.

To summarize, this XXE is useless so far. Even though we can technically trigger the XXE, it only allows us to reach our own server, which we can also achieve with the intended functionalities of our SharePoint sources (such as XmlDataSource). We need to figure out something else.

SPXmlDataSource and URL Parsing Issues

At this point, I was not able to abuse the HTTP-based sources. I tried to use SPXmlDataSource with the following request:

       /sites/mysite/test.xml

The idea is simple. We are a SharePoint user, and we can upload files to some sites. We upload our malicious XML to the http://sharepoint/sites/mysite/test.xml document and then we:
       • Create SPXmlDataSource
       • Set DataFile to /sites/mysite/test.xml.

SPXmlDataSource will successfully retrieve our XML. What about XmlSecureResolver? Unfortunately, such a path (without a protocol) will lead to a very restrictive policy, which does not allow us to leverage this XXE.

It made me wonder about the URL parsing. I knew that I could not abuse HTTP-based XmlDataSource and SoapDataSource. The code was written in C# and it was pretty straightforward to read – URL parsing looked good there. On the other hand, the URL parsing of SPXmlDataSource is performed by some unmanaged code, which cannot be easily decompiled and read.

I started thinking about a following potential exploitation scenario:
       • Delivering a “malformed” URL.
       • SPXmlDataSource somehow manages to handle this URL, and retrieves my uploaded XML successfully.
       • The URL gives me an unrestricted XmlSecureResolver policy and I’m able to fully exploit XXE.

This idea seemed good, and I decided to investigate the possibilities. First, we have to figure out when XmlSecureResolver gives us a nice policy, which allows us to:
       • Access a local file system (to read file contents).
       • Perform HTTP communication to any server (to exfiltrate data).

Let’s deliver the following URL to XmlSecureResolver:

       file://localhost/c$/whatever

Bingo! XmlSecureResolver creates a policy with no restrictions! It thinks that we are loading the XML from the local file system, which means that we probably already have full access, and we can do anything we want.

Such a URL is not something that we should be able to deliver to SPXmlDataSource or any other data source that we have available. None of them is based on the local file system, and even if they were, we are not able to upload files there.

Still, we don’t know how SPXmlDataSource is handling URLs. Maybe my dream attack scenario with a malformed URL is possible? Before even trying to reverse the appropriate function, I started playing around with this SharePoint data source, and surprisingly, I found a solution quickly:

       file://localhost\c$/sites/mysite/test.xml

Let’s see how SPXmlDataSource handles it (based on my observations):

Figure 4 SPXmlDataSource - handling of malformed URL

This is awesome. Such a URL allows us to retrieve the XML that we can freely upload to SharePoint. On the other hand, it gives us an unrestricted access policy in XmlSecureResolver! This URL parsing confusion between those two components gives us the possibility to fully exploit the XXE and perform a file read.

The entire attack scenario looks like this:

Figure 5 SharePoint XXE - entire exploitation scenario

Demo

Let’s have a look at the demo, to visualize things better. It presents the full exploitation process, together with the debugger attached. You can see that:
       • SPXmlDataSource fetches the malicious XML file, even though the URL is malformed.
       • XmlSecureResolver creates an unrestricted access policy.
       • XXE is exploited and we retrieve the win.ini file.
       • “DTD prohibited” exception is eventually thrown, but we were still able to abuse the OOB XXE.

The Patch

The patch from Microsoft implemented two main changes:
       • More URL parsing controls for SPXmlDataSource.
       • XmlTextReader object also prohibits DTD usage (previously, only XmlReaderSettings did that).

In general, I find .NET XXE-protection settings way trickier than the ones that you can define in various Java parsers. This is because you can apply them to objects of different types (here: XmlReaderSettings versus XmlTextReader). When XmlTextReader prohibits the DTD usage, parameter entities seem to never be resolved, even with the resolver specified (that’s how this patch works). On the other hand, when XmlReaderSettings prohibits DTDs, parameter entities are resolved when the XmlUrlResolver is used. You can easily get confused here.

Summary

A lot of us thought that XXE vulnerabilities were almost dead in .NET. Still, it seems that you may sometimes spot some tricky implementations and corner cases that may turn out to be vulnerable. A careful review of .NET XXE-related settings is not an easy task (they are tricky) but may eventually be worth a shot.

I hope you liked this writeup. I have a huge line of upcoming blog posts, but vulnerabilities are waiting for the patches (including one more SharePoint vulnerability). Until my next post, you can follow me @chudypb and follow the team on Twitter, Mastodon, LinkedIn, or Instagram for the latest in exploit techniques and security patches.

❌
❌