PermissionSet currentPermissionSet = null ; if (this.TypeFilterLevel != TypeFilterLevel.Full) { currentPermissionSet = new PermissionSet(PermissionState.None); currentPermissionSet .SetPermission(

new SecurityPermission(

SecurityPermissionFlag.SerializationFormatter)); } try { if ( currentPermissionSet != null ) currentPermissionSet .PermitOnly(); // Deserialize Request - Stream to IMessage requestMsg = CoreChannel.DeserializeBinaryRequestMessage(

objectUri , requestStream , _strictBinding, this.TypeFilterLevel);

} finally { if ( currentPermissionSet != null ) CodeAccessPermission.RevertPermitOnly(); }

ExampleRemotingService.exe -t low

ExploitRemotingService.exe --useser tcp://127.0.0.1:12345/RemotingServer ls c:\

ExploitRemotingService.exe --uselease tcp://127.0.0.1:12345/RemotingServer ls c:\

ExploitRemotingService.exe --uselease --autodir tcp://127.0.0.1:12345/RemotingServer exec notepad

As we're passing thecontaining the serialized object we want to capture as well as the MBRas the top level object all of our machinations will run during thisgrant, which as I've already noted will fail. If we could defer the deserialization, or at least any privileged operation until after the CAS grant is reverted we'd be able to exploit this trick, but how can we do that?One way to defer code execution is to exploit object finalization. Basically when an object's resources are about to be reclaimed by the GC it'll call the object's finalizer . This call is made on a GC thread completely outside the deserialization process and so wouldn't be affected by the CASgrant. In fact abusing finalizers was something I pointed out in my original research on .NET serialization, a good example is the infamous TempFileCollection class.I thought about trying to find a useful gadget to exploit this, however there were two problems. First the difficulty in finding a suitable object which is both serializable and has a useful finalizer defined and second, the call to the finalizer is non-deterministic as it's whenever the GC gets called. In theory the GC might never be called.I decided to focus on a different approach based on a non-obvious observation. Thesecurity behaviors ofonly apply when calling a method on a server object, not deserializing the return value. Therefore if I could find somewhere in the server which calls back to a MBR object I control then I can force the server to deserialize an arbitrary object. This object can be used to mount the attack as the deserialization would not occur under theCAS grant and I can use the sametrick to capture aorobject.In theory you could find an exposed method on the server object to use for this callback, however I wanted my code to be generic and not require knowledge of the server object outside of the knowing the URI. Therefore it'd have to be a method we can call on the MBR or baseclass. An initial look only shows one candidate, themethod which takes a single parameter. Unfortunately most of the time a server object won't override this method and the default just performs reference equality which doesn't call any methods on the passed object.The only other candidates are the InitializeLifetimeServer or GetLifetimeService methods which return an MBR which implements the ILease interface. I'm not going to go into what this is used for (you can read up on it on MSDN ) but what I noticed was theinterface has a Register method which takes an object which implementsinterface. If you registered an MBR object in the client with the server's lifetime service then when the server wants to check if the object should be destroyed it'll call the ISponsor::Renewal method, which gives us our callback. While the method doesn't return an object, we can just throw an exception with theinside and exploit the service. Victory?Not quite, it turns out that we've now got new problems. The first one is thecall only happens when the lifetime counter expires, the default timeout is around 10 minutes from the last call to the server. This means that our exploit will only run at some long, potentially indeterminate point in time. Not the end of the world, but as frustrating as waiting for a GC run to get a finalizer executed. But the second problem seems more insurmountable, in order to set theobject we need to make an actual call to the server, howeverwould stop us from passing an MBRobject as the top level object would be arecord type which would throw an exception when it was encountered during argument deserialization.What can we do? Turns out there's an easy way around this, the framework provides us with a full serializable MethodCall class. Instead of using therecord type we can instead package up a serializableobject as the top level object with all the data needed to make the call to. As the top level object is using a normal serialized object record type and not arecord type it'll never trigger the type checking and we can callwith our MBRobject.You might wonder if there's another problem here, won't deserializing the MBR cause the channel to be created and hit theCAS grant? Fortunately channel setup is deferred until a call is made on the object, therefore as long as no call is made to the MBR object during the deserialization process we'll be out of the CAS grant and able to setup the channel when themethod is called.We now have a way of exploiting the remoting service without knowledge of any specific methods on the server object, the only problem is we might need to wait 10 minutes to do it. Can we improve on the time? Digging further into default remoting implementation I noticed that if an argument being passed to a method isn't directly of the required type the methodwill callto try the coerce the argument to the correct type. The fallback is to call Convert::ChangeType passing the needed type and the object passed from the client. To convert to the correct type the code will see if the passed object implements the IConvertible interface and call the ToType method on it. Therefore, instead of passing an implementation oftowe just pass one which implementsthe remoting code will try and coerce it usingwhich will give us our needed callback immediately without waiting 10 minutes. I've summarized the attack in the following diagram:This entire exploit is implemented behind theoption. It works in the same way asbut should work even if the server is runningmode. Of course there's caveats, this only works if the server sets up a bi-direction channel, if it registers aorthen that should be fine, but if it just sets up ait might not work. Also you still need to know the URI of the server and bypass any authentication requirements.If you want to try it out grab the code from github and compile it. First run thewith the following command line:This will run the example service with. Now you can trywith the following command line:You should notice it fails. Now changetoand rerun the command:You should see a directory listing of the C: drive. Finally if you pass theoption the exploit tool will try and upload an assembly to the server's base directory and bootstrap a full server from which you can call other commands such as exec.If it all works you should find the example server will spawn notepad. This works on a fully up to date version of .NET (e.g. .NET 4.8).The take away from this is DO NOT EVER USE .NET REMOTING IN PRODUCTION. Even if you're lucky and you're not exploitable for some reason the technologies should be completely deprecated and (presumably) will never be ported .NET Core.