Friday, April 28, 2017

Exploiting .NET Managed DCOM

Posted by James Forshaw, Project Zero

One of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 more interesting classes of security vulnerabilities are those affecting interoperability technology. This is because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se vulnerabilities typically affect any application using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 technology, regardless of what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 application actually does. Also in many cases cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y’re difficult for a developer to mitigate outside of not using that technology, something which isn’t always possible.

I discovered one such vulnerability class in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Component Object Model (COM) interoperability layers of .NET which make cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use of .NET for Distributed COM (DCOM) across privilege boundaries inherently insecure. This blog post will describe a couple of ways this could be abused, first to gain elevated privileges and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n as a remote code execution vulnerability.

A Little Bit of Background Knowledge

If you look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 history of .NET many of its early underpinnings was trying to make a better version of COM (for a quick history lesson it’s worth watching this short video of Anders Hejlsberg discussing .NET). This led to Microsoft placing a large focus on ensuring that while .NET itself might not be COM it must be able to interoperate with COM. Therefore .NET can both be used to implement as well as consume COM objects. For example instead of calling QueryInterface on a COM object you can just cast an object to a COM compatible interface. Implementing an out-of-process COM server in C# is as simple as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

// Define COM interface.
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("3D2392CB-2273-4A76-9C5D-B2C8A3120257")]
public interface ICustomInterface {
   void DoSomething();
}

// Define COM class implementing interface.
[ComVisible(true)]
[Guid("8BC3F05E-D86B-11D0-A075-00C04FB68820")]
public class COMObject : ICustomInterface {
   public void DoSomething() {}
}

// Register COM class with COM services.
RegistrationServices reg = new RegistrationServices();
int cookie = reg.RegisterTypeForComClients(
                 typeof(COMObject),
                 RegistrationClassContext.LocalServer
                   | RegistrationClassContext.RemoteServer,
                 RegistrationConnectionType.MultipleUse);

A client can now connect to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM server using it’s CLSID (defined by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Guid attribute on COMClass). This is in fact so simple to do that a large number of core classes in .NET are marked as COM visible and registered for use by any COM client even those not written in .NET.

com_category.PNG

To make this all work cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET runtime hides a large amount of boilerplate from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 developer. There are a couple of mechanisms to influence this boilerplate interoperability code, such as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 InterfaceType attribute which defines whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM interface is derived from IUnknown or IDispatch but for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most part you get what you’re given.

One thing developers perhaps don’t realize is that it’s not just cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interfaces you specify which get exported from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET COM object but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime adds a number of “management” interfaces as well. This interfaces are implemented by wrapping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET object inside a COM Callable Wrapper (CCW).

NET CCW (1).png
We can enumerate what interfaces are exposed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CCW. Taking System.Object as an example cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following table shows what interfaces are supported along with how each interface is implemented, eicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r dynamically at runtime or statically implemented inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime.

Interface Name
Implementation Type
_Object
Dynamic
IConnectionPointContainer
Static
IDispatch
Dynamic
IManagedObject
Static
IMarshal
Static
IProvideClassInfo
Static
ISupportErrorInfo
Static
IUnknown
Dynamic

The _Object interface refers to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM visible representation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 System.Object class which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 root of all .NET objects, it must be generated dynamically as it’s dependent on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET object being exposed. On cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r hand IManagedObject is implemented by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime itself and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation is shared across all CCWs.

I started looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exposed COM attack surface for .NET back in 2013 when I was investigating Internet Explorer sandbox escapes. One of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM objects you could access outside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sandbox was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET ClickOnce Deployment broker (DFSVC) which turned out to be implemented in .NET, which is probably not too surprising. I actually found two issues, not in DFSVC itself but instead in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 _Object interface exposed by all .NET COM objects. The _Object interface looks like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following (in C++).

struct _Object : public IDispatch {
 HRESULT ToString(BSTR * pRetVal);
 HRESULT Equals(VARIANT obj, VARIANT_BOOL *pRetVal);
 HRESULT GetHashCode(long *pRetVal);
 HRESULT GetType(_Type** pRetVal);
};

The first bug (which resulted in CVE-2014-0257) was in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GetType method. This method returns a COM object which can be used to access cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET reflection APIs. As cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 returned _Type COM object was running inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server you could call a chain of methods which resulted in getting access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Process.Start method which you could call to escape cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sandbox. If you want more details about that you can look at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PoC I wrote and put up on Github. Microsoft fixed this by preventing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 access to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reflection APIs over DCOM.

The second issue was more subtle and is a byproduct of a feature of .NET interop which presumably no-one realized would be a security liability. Loading cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET runtime requires quite a lot of additional resources, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 default for a native COM client calling methods on a .NET COM server is to let COM and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CCW manage cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 communication, even if this is a performance hit. Microsoft could have chosen to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM marshaler to force .NET to be loaded in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client but this seems overzealous, not even counting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 possibility that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client might not even have a compatible version of .NET installed.

When .NET interops with a COM object it creates cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 inverse of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CCW, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Runtime Callable Wrapper (RCW). This is a .NET object which implements a runtime version of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM interface and marshals it to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM object. Now it’s entirely possible that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM object is actually written in .NET, it might even be in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same Application Domain. If .NET didn’t do something you could end up with a double performance hit, marshaling in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RCW to call a COM object which is actually a CCW to a managed object.

NET RCW (1).png


It would be nice to try and “unwrap” cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 managed object from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CCW and get back a real .NET object. This is where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 villain in this piece comes into play, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IManagedObject interface, which looks like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

struct IManagedObject : public IUnknown {
 HRESULT GetObjectIdentity(
   BSTR*   pBSTRGUID,  
   int*    AppDomainID,  
   int*    pCCW);
   
 HRESULT GetSerializedBuffer(
   BSTR *pBSTR  
 );
};

When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET runtime gets hold of a COM object it will go through a process to determine whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r it can “unwrap” cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object from its CCW and avoid creating an RCW. This process is documented but in summary cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime will do cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:
  1. Call QueryInterface on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM object to determine if it implements cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IManagedObject interface. If not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n return an appropriate RCW.
  2. Call GetObjectIdentity on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interface. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GUID matches cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 per-runtime GUID (generated at runtime startup) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 AppDomain ID matches cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current AppDomain ID cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n lookup cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CCW value in a runtime table and extract a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 real managed object and return it.
  3. Call GetSerializedBuffer on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 interface. The runtime will check if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET object is serializable, if so it will pass cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object to BinaryFormatter::Serialize and package cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 result in a Binary String (BSTR). This will be returned to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client which will now attempt to deserialize cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buffer to an object instance by calling BinaryFormatter::Deserialize.

Both steps 2 and 3 sound like a bad idea. For example while in 2 cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 per-runtime GUID can’t be guessed; if you have access to any ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r object in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same process (such as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM object exposed by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server itself) you can call GetObjectIdentity on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object and replay cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GUID and AppDomain ID back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server. This doesn’t really gain you much though, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CCW value is just a number not a pointer so at best you’ll be able to extract objects which already have a CCW in place.

Instead it’s step 3 which is really nasty. Arbitrary deserialization is dangerous almost no matter what language (take your pick, Java, PHP, Ruby etc.) and .NET is no different. In fact my first ever Blackhat USA presentation (whitepaper) was on this very topic and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s been follow up work since (such as this blog post). Clearly this is an issue we can exploit, first let’s look at it from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 perspective of privilege escalation.

Elevating Privileges

How can we get a COM server written in .NET to do cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 arbitrary deserialization? We need cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server to try and create an RCW for a serializable .NET object exposed over COM. It would be nice if this could also been done generically; it just so happens that on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 standard _Object interface cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re exists a function we can pass an arbitrary object to, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Equals method. The purpose of Equals is to compare two objects for equality. If we pass a .NET COM object to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server’s Equals method cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime must try and convert it to an RCW so that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 managed implementation can use it. At this point cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime wants to be helpful and checks if it’s really a CCW wrapped .NET object. The server runtime calls GetSerializedBuffer which results in arbitrary deserialization in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server process.

This is how I exploited cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ClickOnce Deployment broker a second time resulting in  CVE-2014-4073. The trick to exploiting this was to send a serialized Hashtable to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server which contains a COM implementation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IHashCodeProvider interface. When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Hashtable runs its custom deserialization code it needs to rebuild its internal hash structures, it does that by calling IHashCodeProvider::GetHashCode on each key. By adding a Delegate object, which is serializable, as one of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 keys we’ll get it passed back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client. By writing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client in native code cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 automatic serialization through IManagedObject won’t occur when passing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegate back to us. The delegate object gets stuck inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server process but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CCW is exposed to us which we can call. Invoking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegate results in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 specified function being executed in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server context which allows us to start a new process with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server’s privileges. As this works generically I even wrote a tool to do it for any .NET COM server which you can find on github.

Deserialization Process EoP (1).png

Microsoft could have fixed CVE-2014-4073 by changing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 behavior of IManagedObject::GetSerializedBuffer but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y didn’t. Instead Microsoft rewrote cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 broker in native code instead. Also a blog post was published warning developers of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dangers of .NET DCOM. However what cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y didn’t do is deprecate any of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 APIs to register DCOM objects in .NET so unless a developer is particularly security savvy and happens to read a Microsoft security blog cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y probably don’t realize it’s a problem.

This bug class exists to this day, for example when I recently received a new work laptop I did what I always do, enumerate what OEM “value add” software has been installed and see if anything was exploitable. It turns out that as part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 audio driver package was installed a COM service written by Dolby. After a couple of minutes of inspection, basically enumerating accessible interface for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM server, I discovered it was written in .NET (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 presence of IManagedObject is always a big giveaway). I cracked out my exploitation tool and in less than 5 minutes I had code execution at local system. This has now been fixed as CVE-2017-7293, you can find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 very terse writeup here. Once again as .NET DCOM is fundamentally unsafe cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 only thing Dolby could do was rewrite cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service in native code.

Hacking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Caller

Finding a new instance of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IManagedObject bug class focussed my mind on its ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r implications. The first thing to stress is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server itself isn’t vulnerable, instead it’s only when we can force cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server to act as a DCOM client calling back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacking application that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability can be exploited. Any .NET application which calls a DCOM object through managed COM interop should have a similar issue, not just servers. Is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re likely to be any common use case for DCOM, especially in a modern Enterprise environment?

My immediate thought was Windows Management Instrumentation (WMI). Modern versions of Windows can connect to remote WMI instances using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WS-Management (WSMAN) protocol but for legacy reasons WMI still supports a DCOM transport. One use case for WMI is to scan enterprise machines for potentially malicious behavior. One of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reasons for this resurgence is  Powershell (which is implemented in .NET) having easy to use support for WMI. Perhaps PS or .NET itself will be vulnerable to this attack if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y try and access a compromised workstation in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network?

Monitoring (3).png

Looking at MSDN, .NET supports WMI through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 System.Management namespace. This has existed since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 beginning of .NET. It supports remote access to WMI and considering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 age of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 classes it predates WSMAN and so almost certainly uses DCOM under cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 hood. On cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PS front cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s support for WMI through cmdlets such as Get-WmiObject. PS version 3 (introduced in Windows 8 and Server 2008) added a new set of cmdlets including Get-CimInstance. Reading cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 related link it’s clear why cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CIM cmdlets were introduced, support for WSMAN, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 link explicitly points out that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “old” WMI cmdlets uses DCOM.

wmi_cmdlets.PNG

At this point we could jump straight into RE of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET and PS class libraries, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s an easier way. It’s likely we’d be able to see whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET client queries for IManagedObject by observing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 DCOM RPC traffic to a WMI server. Wireshark already has a DCOM dissector saving us a lot of trouble. For a test I set up two VMs, one with Windows Server 2016 acting as a domain controller and one with Windows 10 as a client on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 domain. Then from a Domain Administrator on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client I issued a simple WMI PS command ‘Get-WmiObject Win32_Process -ComputerName dc.network.local’ while monitoring cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network using Wireshark. The following image shows what I observed:

wmi_dcom.PNG

The screenshot shows cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 initial creation request for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WMI DCOM object on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 DC server (192.168.56.50) from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PS client (192.168.56.102). We can see it’s querying for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IWbemLoginClientID interface which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 initialization process (as documented in MS-WMI). The client cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n tries to request a few ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r interfaces; notably it asks for IManagedObject. This almost certainly indicates that a client using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PS WMI cmdlets would be vulnerable.

In order to test whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r this is really a vulnerability we’ll need a fake WMI server. This would seem like it would be quite a challenge, but all we need to do is modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 registration for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 winmgmt service to point to our fake implementation. As long as that service cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n registers a COM class with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 CLSID {8BC3F05E-D86B-11D0-A075-00C04FB68820} cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 COM activator will start cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 service and serve any client an instance of our fake WMI object. If we look back at our network capture it turns out that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 query for IManagedObject isn’t occurring on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main class, but instead on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IWbemServices object returned from IWbemLevel1Login::NTLMLogin. But that’s okay, it just adds a bit extra boilerplate code. To ensure it’s working we’ll implement cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following code which will tell cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 deserialization code to look for an unknown Assembly called Badgers.

[Serializable, ComVisible(true)]
public class FakeWbemServices :
              IWbemServices,
              ISerializable {
   public void GetObjectData(SerializationInfo info,
                             StreamingContext context) {
       info.AssemblyName = "Badgers, Version=4.0.0.0";
       info.FullTypeName = "System.Badgers.Test";
   }
   
   // Rest of fake implementation...
}

If we successfully injected a serialized stream cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we’d expect cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PS process to try and lookup a Badgers.dll file and using Process Monitor that’s exactly what we find.

badgers_assembly.PNG

Chaining Up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Deserializer

When exploiting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 deserialization for local privilege escalation we can be sure that we can connect back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 server and run an arbitrary delegate. We don’t have any such guarantees in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RCE case. If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WMI client has default Windows Firewall rules enabled cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n we almost certainly wouldn’t be able to connect to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RPC endpoint made by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegate object. We also need to be allowed to login over cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 network to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 machine running cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WMI client, our compromised machine might not have a login to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 domain or cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 enterprise policy might block anyone but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 owner from logging in to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client machine.

We cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore need a slightly different plan, instead of actively attacking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 client through exposing a new delegate object we’ll instead pass it a byte stream which when deserialized executes a desired action. In an ideal world we’d find that one serializable class which just executes arbitrary code for us. Sadly (as far as I know of) no such class exists. So instead we’ll need to find a series of “Gadget” classes which when chained togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r perform cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 desired effect.

So in this situation I tend to write some quick analysis tools, .NET supports a pretty good Reflection API so finding basic information such as whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r a class is serializable or which interfaces a class supports is pretty easy to do. We also need a list of Assemblies to check, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 quickest way I know of is to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 gacutil utility installed as part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET SDK (and so installed with Visual Studio). Run cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 command gacutil /l > assemblies.txt to create a list of assembly names you can load and process. For a first pass we’ll look for any classes which are serializable and have delegates in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se might be classes which when an operation is performed will execute arbitrary code. With our list of assemblies we can write some simple code like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following to find those classes, just call FindSerializableTypes for each assembly name string:

static bool IsDelegateType(Type t) {
 return typeof(Delegate).IsAssignableFrom(t);
}

static bool HasSerializedDelegate(Type t) {
 // Custom serialized objects rarely serialize cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir delegates.
 if (typeof(ISerializable).IsAssignableFrom(t)) {
   return false;
 }

 foreach (FieldInfo field in FormatterServices.GetSerializableMembers(t)) {
   if (IsDelegateType(field.FieldType)) {
     return true;
   }
 }
}

static void FindSerializableTypes(string assembly_name) {
 Assembly asm = Assembly.Load(assembly_name);
 var types = asm.GetTypes().Where(t =>   t.IsSerializable
                                     &&  t.IsClass
                                     && !t.IsAbstract
                                     && !IsDelegateType(t)
                                     &&  HasSerializedDelegate(t));
 foreach (Type type in types) {
   Console.WriteLine(type.FullName);
 }
}

Across my system this analysis only resulted in around 20 classes, and of those many were actually in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 F# libraries which are not distributed in a default installation. However one class did catch my eye, System.Collections.Generic.ComparisonComparer. You can find cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference source, but as it’s so simple here it is in its entirety:

public delegate int Comparison<T>(T x, T y);

[Serializable]
internal class ComparisonComparer<T> : Comparer<T> {
 private readonly Comparison<T> _comparison;

 public ComparisonComparer(Comparison<T> comparison) {
   _comparison = comparison;
 }

 public override int Compare(T x, T y) {
   return this._comparison(x, y);
 }
}

This class wraps a Comparison delegate which takes two generic parameters (of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same type) and returns an integer, calling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegate to implement cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 IComparer interface. While cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 class is internal its creation is exposed through Comparer::Create static method. This is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chain, with this class and a bit of massaging of serialized delegates we can chain IComparer::Compare to Process::Start and get an arbitrary process created. Now we need cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chain, calling this comparer object with arbitrary arguments.

Comparer objects are used a lot in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 generic .NET collection classes and many of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se collection classes also have custom deserialization code. In this case we can abuse cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SortedSet class, on deserialization it rebuilds its set using an internal comparer object to determine cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sort order. The values passed to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 comparer are cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entries in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 set, which is under our complete control.  Let’s write some test code to check it works as we expect:

static void TypeConfuseDelegate(Comparison<string> comp) {
   FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList",
           BindingFlags.NonPublic | BindingFlags.Instance);
   object[] invoke_list = comp.GetInvocationList();
   // Modify cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 invocation list to add Process::Start(string, string)
   invoke_list[1] = new Func<string, string, Process>(Process.Start);
   fi.SetValue(comp, invoke_list);
}

// Create a simple multicast delegate.
Delegate d = new Comparison<string>(String.Compare);
Comparison<string> d = (Comparison<string>) MulticastDelegate.Combine(d, d);
// Create set with original comparer.
IComparer<string> comp = Comparer<string>.Create(d);
SortedSet<string> set = new SortedSet<string>(comp);

// Setup values to call calc.exe with a dummy argument.
set.Add("calc");
set.Add("adummy");

TypeConfuseDelegate(d);

// Test serialization.
BinaryFormatter fmt = new BinaryFormatter();
MemoryStream stm = new MemoryStream();
fmt.Serialize(stm, set);
stm.Position = 0;
fmt.Deserialize(stm);
// Calculator should execute during Deserialize.

The only weird thing about this code is TypeConfuseDelegate. It’s a long standing issue that .NET delegates don’t always enforce cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir type signature, especially cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 return value. In this case we create a two entry multicast delegate (a delegate which will run multiple single delegates sequentially), setting one delegate to String::Compare which returns an int, and anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r to Process::Start which returns an instance of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Process class. This works, even when deserialized and invokes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 two separate methods. It will cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n return cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 created process object as an integer, which just means it will return cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 instance of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process object. So we end up with chain which looks like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

SortedSet Deserialization (1).png

While this is a pretty simple chain it has a couple of problems which makes it less than ideal for our use:
  1. The Comparer::Create method and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 corresponding class were only introduced in .NET 4.5, which covers Windows 8 and above but not Windows 7.
  2. The exploit relies in part on a type confusion of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 return value of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegate. While it’s only converting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Process object to an integer this is somewhat less than ideal and could have unexpected side effects.
  3. Starting a process is a bit on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 noisy side, it would be nicer to load our code from memory.

So we’ll need to find something better. We want something which works at a minimum on .NET 3.5, which would be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 version on Windows 7 which Windows Update would automatically update you to. Also it shouldn’t rely on undefined behaviour or loading our code from outside of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 DCOM channel such as over a HTTP connection. Sounds like a challenge to me.

Improving cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chain

While looking at some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r classes which are serializable I noticed a few in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 System.Workflow.ComponentModel.Serialization namespace. This namespace contains classes which are part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Windows Workflow Foundation, which is a set of libraries to build execution pipelines to perform a series of tasks. This alone sounds interesting, and it turns out I have exploited cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 core functionality before as a bypass for Code Integrity in Windows Powershell.

This lead me to finding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ObjectSerializedRef class. This looks very much like a class which will deserialize any object type, not just serialized ones. If this was cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n that would be a very powerful primitive for building a more functional deserialization chain.

[Serializable]
private sealed class ObjectSerializedRef : IObjectReference,
                                          IDeserializationCallback
{
 private Type type;
 private object[] memberDatas;

 [NonSerialized]
 private object returnedObject;

 object IObjectReference.GetRealObject(StreamingContext context) {
   returnedObject = FormatterServices.GetUninitializedObject(type);
   return this.returnedObject;
 }

 void IDeserializationCallback.OnDeserialization(object sender) {
   string[] array = null;
   MemberInfo[] serializableMembers =
      FormatterServicesNoSerializableCheck.GetSerializableMembers(
          type, out array);
   FormatterServices.PopulateObjectMembers(returnedObject,
                           serializableMembers, memberDatas);
 }
}

Looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 class was used as a serialization surrogate exposed through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ActivitiySurrogateSelector class. This is a feature of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET serialization API, you can specify a “Surrogate Selector” during cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 serialization process which will replace an object with surrogate class. When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stream is deserialized this surrogate class contains enough information to reconstruct cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original object. One use case is to handle cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 serialization of non-serializable classes, but ObjectSerializedRef goes beyond a specific use case and allows you to deserialize anything. A test was in order:

// Definitely non-serializable class.
class NonSerializable {
 private string _text;

 public NonSerializable(string text) {
   _text = text;
 }

 public override string ToString() {
   return _text;
 }
}

// Custom serialization surrogate
class MySurrogateSelector : SurrogateSelector {
 public override ISerializationSurrogate GetSurrogate(Type type,
     StreamingContext context, out ISurrogateSelector selector) {
   selector = this;
   if (!type.IsSerializable) {
     Type t = Type.GetType("ActivitySurrogateSelector+ObjectSurrogate");
     return (ISerializationSurrogate)Activator.CreateInstance(t);
   }

   return base.GetSurrogate(type, context, out selector);
 }
}

static void TestObjectSerializedRef() {
   BinaryFormatter fmt = new BinaryFormatter();
   MemoryStream stm = new MemoryStream();
   fmt.SurrogateSelector = new MySurrogateSelector();
   fmt.Serialize(stm, new NonSerializable("Hello World!"));
   stm.Position = 0;

   // Should print Hello World!.
   Console.WriteLine(fmt.Deserialize(stm));
}

The ObjectSurrogate class seems to work almost too well. This class totally destroys any hope of securing an untrusted BinaryFormatter stream and it’s available from .NET 3.0. Any class which didn’t mark itself as serializable is now a target. It’s going to be pretty easy to find a class which while invoke an arbitrary delegate during deserialization as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 developer will not be doing anything to guard against such an attack vector.

Now just to choose a target to build out our deserialization chain. I could have chosen to poke furcá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Workflow classes, but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 API is horrible (in fact in .NET 4 Microsoft replaced cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 old APIs with a new, slightly nicer one). Instead I’ll pick a really easy to use target, Language Integrated Query (LINQ).

LINQ was introduced in .NET 3.5 as a core language feature. A new SQL-like syntax was introduced to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 C# and VB compilers to perform queries across enumerable objects, such as Lists or Dictionaries. An example of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 syntax which filters a list of names based on length and returns cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list uppercased is as follows:

string[] names = { "Alice", "Bob", "Carl" };

IEnumerable<string> query = from name in names
                           where name.Length > 3
                           orderby name
                           select name.ToUpper();

foreach (string item in query) {
   Console.WriteLine(item);
}

You can also view LINQ not as a query syntax but instead a way of doing list comprehension in .NET. If you think of ‘select’ as equivalent to ‘map’ and ‘where’ to ‘filter’ it might make more sense. Underneath cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 query syntax is a series of methods implemented in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 System.Linq.Enumerable class. You can write it using normal C# syntax instead of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 query language; if you do cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous example becomes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

IEnumerable<string> query = names.Where(name => name.Length > 3)
                                .OrderBy(name => name)
                                .Select(name => name.ToUpper());

The methods such as Where take two parameters, a list object (this is hidden in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above example) and a delegate to invoke for each entry in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 enumerable list. The delegate is typically provided by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 application, however cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s nothing to stop you replacing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegates with system methods. The important thing to bear in mind is that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegates are not invoked until cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list is enumerated. This means we can build an enumerable list using LINQ methods, serialize it using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ObjectSurrogate (LINQ classes are not cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365mselves serializable) cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n if we can force cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 deserialized list to be enumerated it will execute arbitrary code.

Using LINQ as a primitive we can create a list which when enumerated maps a byte array to an instance of a type in that byte array by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following sequence:

Linq Chain (3).png
The only tricky part is step 2, we’d like to extract a specific type but our only real option is to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Enumerable.Join method which requires some weird kludges to get it to work. A better option would have been to use Enumerable.Zip but that was only introduced in .NET 4. So instead we’ll just get all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 types in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 loaded assembly and create cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m all, if we just have one type cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n this isn’t going to make any difference. How does cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation look in C#?

static IEnumerable CreateLinq(byte[] assembly) {
 List<byte[]> base_list = new List<byte[]>();
 base_list.Add(assembly);
 
 var get_types_del = (Func<Assembly, IEnumerable<Type>>)
                        Delegate.CreateDelegate(
                          typeof(Func<Assembly, IEnumerable<Type>>),
                          typeof(Assembly).GetMethod("GetTypes"));

 return base_list.Select(Assembly.Load)
                 .SelectMany(get_types_del)
                 .Select(Activator.CreateInstance);
}

The only non-obvious part of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 C# implementation is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegate for Assembly::GetTypes. What we need is a delegate which takes an Assembly object and returns a list of Type objects. However as GetTypes is an instance method cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 default would be to capture cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Assembly class and store it inside cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 delegate object, which would result in a delegate which took no parameters and returned a list of Type. We can get around this by using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reflection APIs to create an open delegate to an instance member. An open delegate doesn’t store cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object instance, instead it exposes it as an additional Assembly parameter, exactly what we want.

With our enumerable list we can get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 assembly loaded and our own code executed, but how do we get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list enumerated to start cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chain? For this decided I’d try and find a class which when calling ToString (a pretty common method) would enumerate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 list. This is easy in Java, almost all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 collection classes have this exact behavior. Sadly it seems .NET doesn't follow Java in this respect. So I modified my analysis tools to try and hunt for gadgets which would get us cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re. To cut a long story short I found a chain from ToString to IEnumerable through three separate classes. The chain looks something like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following:

ToString to IEnumerable.png
Are we done yet? No, just one more step, we need to call ToString on an arbitrary object during deserialization. Of course I wouldn’t have chosen ToString if I didn’t already have a method to do this. In this final case I’ll go back to abusing poor, old, Hashtable. During deserialization of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Hashtable class it will rebuild its key set, which we already know about as this is how I exploited serialization for local EoP. If two keys are equal cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 deserialization will fail with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Hashtable throwing an exception, resulting in running cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following code:

throw new ArgumentException(
           Environment.GetResourceString("Argument_AddingDuplicate__",
                                   buckets[bucketNumber].key, key));

It’s not immediately obvious why this would be useful. But perhaps looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 implementation of GetResourceString will make it clearer:

internal static String GetResourceString(String key, params Object[] values) {
   String s = GetResourceString(key);
   return String.Format(CultureInfo.CurrentCulture, s, values);
}

The key is passed to GetResourceString within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 values array as well as a reference to a resource string. The resource string is looked up and along with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 key passed to String.Format. The resulting resource string has formatting codes so when String.Format encounters cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 non-string value it calls ToString on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object to format it. This results in ToString being called during deserialization kicking off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chain of events which leads to us loading an arbitrary .NET assembly from memory and executing code in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 context of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WMI client.

Full Chain.png
You can see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 final implementation in latest cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 PoC I’ve added to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue tracker.

Conclusions

Microsoft fixed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 RCE issue by ensuring that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 System.Management classes never directly creates an RCW for a WMI object. However this fix doesn’t affect any ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r use of DCOM in .NET, so privileged .NET DCOM servers are still vulnerable and ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r remote DCOM applications could also be attacked.

Also this should be a lesson to never deserialize untrusted data using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 .NET BinaryFormatter class. It’s a dangerous thing to do at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 best of times, but it seems that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 developers have abandoned any hope of making secure serializable classes. The presence of ObjectSurrogate effectively means that every class in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime is serializable, whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 original developer wanted cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m to be or not.

And as a final thought you should always be skeptical about cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security implementation of middleware especially if you can’t inspect what it does. The fact that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue with IManagedObject is designed in and hard to remove makes it very difficult to fix correctly.

1 comment:

  1. All cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerabilities you mentioned about BinaryFormatter could be mitigated by using a 'TypeLimiting' SerializationBinder with a strict whitelist of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 expected types to be deserialized right?

    Can you think of a BinaryFormatter exploit that could work even if a type limiting binder is used? (as long as no dangerous types are in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whitelist of course)

    Thanks

    ReplyDelete