Last month, Microsoft released patches to address two remote code execution (RCE) vulnerabilities in SharePoint. In both Critical-rated cases, an attacker could send a specially crafted request to execute their code in the context of the SharePoint application pool and the SharePoint server farm account. Both of these bugs were reported to the ZDI program by Markus Wulftange. He has graciously provided the following write-up on the details of CVE-2019-0604.
When searching for new vulnerabilities, one approach is the bottom-up approach. It describes the approach of looking for an interesting sink and tracing the control and data flow backwards to find out if the sink can be reached.
One of these promising sinks is the deserialization using the XmlSerializer. In general, it is considered a secure serializer as it must be instrumented with the expected type and it is not possible to specify an arbitrary type within the stream that cannot appear in the object graph of the expected type. But it is exploitable if the expected type can be controlled as well, as it has been shown in Friday the 13th – JSON Attacks by Alvaro Muñoz & Oleksandr Mirosh [PDF].
For analyzing the SharePoint 2016 assemblies, dnSpy is an excellent tool as it can be used for both decompiling and debugging of .NET applications. So, after dnSpy is attached to the IIS worker process w3wp.exe that is running SharePoint 2016, and the assemblies have been loaded, the usage of the XmlSerializer(Type)
constructor can be analyzed. Now the tedious part begins where every one of the XmlSerializer(Type)
constructor calls has to be looked at and to check whether the expected type is variable at all (e.g. it is not hard-coded as in new XmlSerializer(typeof(DummyType))
) and whether it is possible to control the type.
One of the methods where the XmlSerializer(Type)
constructor gets called is the Microsoft.SharePoint.BusinessData.Infrastructure.EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
method in Microsoft.SharePoint.dll. The same type with the same functionality is also in the Microsoft.Office.Server.ApplicationRegistry.Infrastructure
namespace in the Microsoft.SharePoint.Portal.dll. We will come back to this later and stick to the one in Microsoft.SharePoint.dll.
Figure 1 : Microsoft.SharePoint.BusinessData.Infrastructure.EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
Here both the typeName
, used to specify the expected type, and the data that gets deserialized originate from text
, which originates from the method's argument encodedId
.
This looks perfect as long as the method gets actually called and the passed parameter can be controlled.
Tracing back the Flow to the Source
The next step is to go through the calls and see if the one of them originates from a point that can be initiated from outside and whether the argument value can also be supplied.
Figure 2: Calls to Microsoft.SharePoint.BusinessData.Infrastructure.EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
If you’re familiar with the ASP.NET, some of the methods might look familiar like Page_Load(object, EventArgs)
or OnLoad(EventArgs)
. They are called during the ASP.NET life cycle, and the types they are defined in extend System.Web.UI.Page, the base type that represents .aspx
files. And, in fact, all three types have a corresponding .aspx
file:
· Microsoft.SharePoint.ApplicationPages.ActionRedirectPage:
/_layouts/15/ActionRedirect.aspx
· Microsoft.SharePoint.ApplicationPages.DownloadExternalData:
/_layouts/15/downloadexternaldata.aspx
· Microsoft.SharePoint.Portal.WebControls.ProfileRedirect:
/_layouts/15/TenantProfileAdmin/profileredirect.aspx
Although in all three cases the parameter value originates from the HTTP request, it is from the URL's query string. That might become a problem as the hex encoding will multiply the length by 4 and thereby can get pretty long and exceed the limit of the HTTP request line.
After further analysis, the last one of all, the ItemPicker.ValidateEntity(PickerEntity)
method, turned out to be a better pick.
Figure 3: ItemPicker.ValidateEntity(PickerEntity)
Here, the PickerEntity
's Key
property of the passed PickerEntity
is used in the EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
call. It gets called by EntityEditor.Validate()
, which iterates each entry stored in the EntityEditor.Entities
property to validate it.
Figure 4: EntityEditor.Validate()
That method gets called by EntityEditor.LoadPostData(string, NameValueCollection)
, which implements the System.Web.UI.IPostBackDataHandler.LoadPostData(string, NameValueCollection)
method.
Figure 5: EntityEditor.LoadPostData(string, NameValueCollection)
So that method gets automatically called on post back requests to ItemPicker
web controls. The call graph looks as follows:
Also note the type hierarchy:
Verifying the Data Flow
Now that there is a way to reach the EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
from an ItemPicker
web control post back, it is still unclear whether the Key
property of a PickerEntity
can be controlled as well.
The EntityEditor.Entities
property is backed by the private field m_listOrder
, which gets only assigned at two points: during instantiation and within the EntityEditor.Validate()
method. In the latter case, it gets the value of the private field m_listOrderTemp
assigned (see line 597 in Fig. 4 above). That field, again, also only gets assigned at two points: during instantiation and within the EntityEditor.ParseSpanData(string)
method. This method is also called by EntityEditor.LoadPostData(string, NameValueCollection)
with the value of an HtmlInputHidden
and the name "hiddenSpanData" (see line 707 in Fig. 5 above). That field's value can be controlled by the user.
What is left is to see what EntityEditor.ParseSpanData(string)
does with the passed data and whether it ends up as a PickerEntity
's Key
. We'll skip that because EntityEditor.ParseSpanData(string)
is pretty long to show and unless it contains special constructs of nested <SPAN>
and <DIV>
tags, which get parsed out, everything else ends up in the PickerEntity
's Key
and then in m_listOrderTemp
list.
So, now we've found and traversed a vector that allows us to reach EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
from an ItemPicker
's post back handling while also having control over the input. What is still left is to find an instance of that web control.
Finding the Entry Point
The ItemPicker
web control is actually never used directly in an .aspx
page. But when looking at the usages of its base type, EntityEditorWithPicker
, it turned out that there is a Picker.aspx
at /_layouts/15/Picker.aspx
that uses it – what a coincidence!
That page expects the type of the picker dialog to use to be provided via the "PickerDialogType" URL parameter in the form of its assembly-qualified name. Here, any of the two ItemPickerDialog
types can be used:
· Microsoft.SharePoint.WebControls.ItemPickerDialog
in Microsoft.SharePoint.dll
· Microsoft.SharePoint.Portal.WebControls.ItemPickerDialog
in Microsoft.SharePoint.Portal.dll
Using the first ItemPickerDialog
type shows the following page:
Figure 6: Picker.aspx with Microsoft.SharePoint.WebControls.ItemPickerDialog
Here, the bottom text field is associated to the ItemPicker
. And there is also the correspondent of the HtmlInputHidden
with the name ctl00$PlaceHolderDialogBodySection$ctl05$hiddenSpanData
that we were looking for. This is the source of our EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
sink.
Proof of Concept
When the form gets submitted with a ctl00$PlaceHolderDialogBodySection$ctl05$hiddenSpanData
value beginning with "__"
(like "__dummy"
), a break point at EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
will reveal the following situation.
Figure 7: Break point at EntityInstanceIdEncoder.DecodeEntityInstanceId(string) with the encodedId value "__dummy"
At that point the call stack looks like this:
And when the other ItemPickerDialog
type is used, just the two topmost entries are different and then look like this:
This is the final proof that the data of ctl00$PlaceHolderDialogBodySection$ctl05$hiddenSpanData
ends up in EntityInstanceIdEncoder.DecodeEntityInstanceId(string)
. The rest is only coping with entity instance id encoding and finding an appropriate XmlSerializer
payload.
After the patch was made available in February, Markus noticed something unusual. The original patch only addressed the Microsoft.SharePoint.BusinessData.Infrastructure.EntityInstanceIdEncoder
in Microsoft.SharePoint.dll but not the Microsoft.Office.Server.ApplicationRegistry.Infrastructure.EntityInstanceIdEncoder
in Microsoft.SharePoint.Portal.dll.
By using the EntityInstanceIdEncoder
type from the Microsoft.SharePoint.Portal.dll with the Picker.aspx
as described here, the exploit still worked even though the patch was installed. Microsoft addressed this with the re-release of CVE-2019-0604 yesterday.
Special thanks to Markus for providing us such a great write-up. Markus can be found on Twitter at @mwulftange, and we certainly hope to see more submissions from him in the future. Until then, follow the team for the latest in exploit techniques and security patches.