Sowe have some "binary file" (with all the text in clear) which is interpreted as JSON-formatted data. A quick search for some of the strings (e.g. k__BackingField) gives us the format of the file: .net c# object. And the form is returning the de-serialized JSON interpretation of the object.
De-serializing has been known for a while as a dangerous operation. A recommended presentation on "De-serialization apocalypse" is -17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf which also presented .NET de-serialization dangers.
The tool of choice to create the payload for such attacks is
ysoserial.net by @pwntester (one of the authors of the presentation above). It includes a bunch of gadgets and plugins, depending on the type and version of framework we try to exploit, and formats to be de-serialized. The format was the easiest part: it's "BinaryFormatter" (option: -f BinaryFormatter).
The next step is to guess which framework is used and which gadget is working there. Perhaps there were some way to recognize the precise one just by analyzing the errors from bad de-serialization, but we just tried all the relevant ones. Our first guess was
ASP.NET for Linux. Reaching nothing, we tried the gadget for Mono too. By the end, it turned out to be the one. The relevant ysoserial gadget is then: -g TypeConfuseDelegateMono.
The missing piece (the one we didn't get on time) is to guess which executables are present on the machine that we'd like to execute (the COMMAND_TO_RUN in the line above). Not so simple since it's a blind attack and the deserialization always returns some error, even if the execution succeeded. We tried all the "usual suspects", like nc, ncat netcat, and the bash+/dev/tcp trick to get a return shell; curl and wget to see if we can get the target to make some requests to our HTTP server; etc.
But none of them were there. Turned out that python (and perl(!)) were probably the only usable executables to create a reverse shell. [on a side note: who doesn't have curl or nc on his machine!!!???]. So that a winning payload looks like:
During a recent security assessment at NCC Group I found a .NET v2.0 application that used .NET Remoting to communicate with its server over HTTP by sending SOAP requests. After decompiling the application I realised that the server had set the TypeFilterLevel to Full which is dangerous as it can potentially lead to remote code execution using deserialisation attacks. However, the exploitation was not as straight forward as I initially expected it to be.
As a result, I performed research to create a guideline for penetration testers in order to make testing in this domain easier in the future. This blog post explains how to find and exploit a vulnerable application that uses .NET Remoting over HTTP using
ysoserial.net gadgets [1].
A .NET project containing a vulnerable client and server has also been created for training purposes and is accessible publicly at [2].
Applications that utilise .NET Remoting can use TCP, IPC, and HTTP channels. James Forshaw has created a brilliant tool to test and exploit TCP and IPC channels [3]. However, I could not find anything for the HTTP channel that uses SOAP messages.
My target used .NET Framework v2.0 but the
ysoserial.net project uses v4.x. This might not always be the case when testing .NET Remoting but having
ysoserial.net that uses .NET v2.0 can come in handy.
If an application has set the TypeFilterLevel to Full, there is no need to actually know about the objects and the SOAPAction header that needs to be sent to the server. The only vital piece of information is to know the service name that is also required when exploiting TCP or IPC channels [3].
In order to generate a SOAP payload using
ysoserial.net, any of the gadgets that supported SoapFormatter could be used. However, one of the following tricks had to be used in order for the payloads to work:
The following HTTP requests show working examples when the TextFormattingRunProperties gadget of
ysoserial.net was used to run the cmd /c calc command. In this example, the service name was VulnerableEndpoint.rem.
The SOAP requests generated by
ysoserial.net at the moment do not crash the server application and only produce errors. It is then possible to use the above error message to identify whether or not an application server is vulnerable.
In order to overcome the final obstacle to test applications that use .NET Framework v2.0, a new
ysoserial.net v2.0 project has been created that can be found in [2]. However, this project only supports a limited number of gadgets, and also requires the target box to have .NET Framework 3.5 installed. Although this is not ideal, it worked on my target as the vulnerable application was running on an updated host that had the newer version of .NET Framework installed as well. An exploit that only relies on .NET Framework 2.0 requires new gadgets to be identified.
It is possible to crash a server application even though the TypeFilterLevel was set to Low. This occurred during testing when the DataSet class was used (see [4]) with a payload generated by the TypeConfuseDelegate gadget of
ysoserial.net as shown below:
The first method of exploitation that was used against server applications can be used here as well. Therefore, SOAP payloads generated by
ysoserial.net could be used after removing the and tags from them.
In order for this attack to work as planned, the Content-Length header in the response should be updated to match the response body size or this header should be removed completely.
Please note that this test may crash the client application unless the errors have been handled gracefully.
As server applications that use .NET Remoting process the incoming SOAP HTTP requests on their own, they do not follow HTTP standards. It is therefore possible to change or remove some important headers such as HOST or replace the HTTP version and verb with a space character. The following example shows valid HTTP request headers that could be used during a deserialisation attack:
It is important to mention that sometimes WAFs are looking for certain values in the header such as the User-Agent or the HOST header to allow a request. Therefore, sending malformed HTTP requests may not always be helpful. In addition to this, this request may fail when going through another proxy or web server such as IIS.
A .NET Remoting HTTP server ignores the charset attribute of the Content-Type header. On the other hand, the XML message in the body can use encodings such as ibm500 or utf-32 to encode the payload. The HTTP Smuggler Burp extension [5] could be used to encode the XML request. However, it was still needed to add the XML prolog with the appropriate encoding immediately before the encoded payload without any CR-LF characters. The following example shows an XML prolog that uses ibm500 encoding:
Microsoft encourage developers to migrate from the legacy .NET Remoting [6] to use Windows Communication Foundation (WCF) [7]. As it has been mentioned in [4], using WCF with DataContractSerializer can improve security of an application to stop deserialisation attacks.
Setting the TypeFilterLevel to Low will also help reducing the risks but it does not eliminate risk of attacks where dangerous whitelisted methods can still be used. During this research, I could not identify a correct way of setting TypeFilterLevel to Low on a client application to stop the exploitation. I will update the provided sample source code [2] to include a safe example in the comments if I find a solution for this.
After digging into the code I found that the objects were serialized using DataContractSerializer. As a bug hunter this is one of the worst serializers to be up against because it is only exploitable in special cases. Specifically, for DataContractSerializer the Type that the data is going to be deserialized into must be controlled by the attacker. There is at least one publicly known example of this in DotNetNuke as referenced by
ysoserial.net.
So it was time to find a new gadget! I spent some time trying to understand how the existing DataContractSerializer gadgets worked. It turns out that the the WindowsIdentity gadget actually uses BinaryFormatter internally when it gets deserialized. This is explained in WindowsIdentityGenerator.cs of
ysoserial.net, and looks like this:
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.
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?
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.
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.
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:
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:
3a8082e126