Feb 27, 2024

Leaking ObjRefs to Exploit HTTP .NET Remoting

Although already considered deprecated in 2009, .NET Remoting is still around. Even where developers might not expect it such as in ASP.NET web applications, both on-premises and on Azure.

In this blog post, we will elaborate on an hidden attack surface in ASP.NET web applications that might unknowingly leak internal object URIs, which can be used to perform .NET Remoting attacks via HTTP, possibly allowing unauthenticated remote code execution.

Introduction

.NET Remoting was already considered a legacy technology back in 2009:

This topic is specific to a legacy technology that is retained for backward compatibility with existing applications and is not recommended for new development. Distributed applications should now be developed using the Windows Communication Foundation (WCF).

And while .NET Framework 4.8 appears to be the final major release of .NET Framework 4.x (i. e., there probably won’t be a 4.9 release) in favor of its cross-platform successor .NET Core (since version 5 only called “.NET”), it still is and certainly also will still be around for several years if not even decades. Many popular Microsoft products such as Exchange, SharePoint, or Skype for Business are built on .NET Framework / ASP.NET technology.

In 2022, our blog post .NET Remoting Revisited already covered the internals and dangers of .NET Remoting in general. While the supported transports TCP and IPC are rather rarely found in the wild, the HTTP channel exposed via IIS / ASP.NET is available by default. So let’s have a look at it.

HTTP Server Channel Chains

.NET Remoting services via HTTP transport can either be provided using a standalone listener or integrated in an ASP.NET web application via IIS. Depending on that, the call stack in front of the server channel chain looks differently:

From there on, the default HTTP server channel sink chain created by HttpServerChannel.CreateDefaultServerProviderChain() looks as follows:

After deserializing the IMessage request message, it gets handed over to the DispatchChannelSink for dispatching.

We will focus on the IIS + ASP.NET scenario.

Calling methods require knowledge of the object’s URI on which the method is to be called on. These may either be well-known names (i.e., registered types that are meant to be made available remotely, often referred to as “service name”) or randomly generated ones for objects that are returned by reference. The latter applies to types that derive from MarshalByRefObject. In such cases, the RemotingSurrogateSelector and RemotingSurrogate ensure that a ObjRef gets created and registered at the server and the ObjRef gets returned to the client instead of the object.

By default, exploiting any of the publicly known vulnerabilities in .NET Remoting via HTTP requires knowledge of a valid object URI. A leaked ObjRef would work, though.

Leaking ObjRefs

One crucial aspect of this vulnerability is that within the call stack there are only two try … catch statements. The one in HttpRemotingHandler only returns a generic textual error message. The other in SoapServerFormatterSink/BinaryServerFormatterSink, however, creates a ReturnMessage and serializes it.

Within the ReturnMessage constructor, the current LogicalCallContext gets assigned to the message. That context “[p]rovides a set of properties that are carried with the execution code path during remote method calls.”

To leak an ObjRef, there are two conditions to be met:

  • There is a way to reach the exception handling in SoapServerFormatterSink/BinaryServerFormatterSink.
  • Some class instance deriving from MarshalByRefObject gets stored in the LogicalCallContext.

We’ll first look at the former.

Trust Issues

In Finding and Exploiting .NET Remoting over HTTP using Deserialisation, Soroush Dalili already noticed that it is possible to overwrite somewhat trusted values returned from HttpRequest with values from corresponding HTTP headers:

The HTTP verb could be changed to any arbitrary verb such as GET as long as the __RequestVerb header was set to POST.

The service name could also be removed from the URL and could be sent in the __requestUri header.

This happens in the HttpHandlerTransportSink.HandleRequest(HttpContext) method:

BaseTransportHeaders requestHeaders = new BaseTransportHeaders();

requestHeaders["__RequestVerb"] = httpRequest.HttpMethod;
requestHeaders["__CustomErrorsEnabled"] = HttpRemotingHandler.CustomErrorsEnabled(context);
requestHeaders.RequestUri = (string)context.Items["__requestUri"];

NameValueCollection headers = httpRequest.Headers;
String[] allKeys = headers.AllKeys;

for (int httpKeyCount=0; httpKeyCount< allKeys.Length; httpKeyCount++)
{
    String headerName = allKeys[httpKeyCount];
    String headerValue = headers[headerName];
    requestHeaders[headerName] = headerValue;
}

This trick can be used to pass validation and proceed to the SoapServerFormatterSink/BinaryServerFormatterSink:

GET /RemoteApplicationMetadata.rem?wsdl HTTP/1.1
Host: localhost
__RequestVerb: POST

Depending on the provided Content-Type, either the SoapServerFormatterSink or BinaryServerFormatterSink processes the request. As the requested object URI is probably not registered to a server identity, an exception gets thrown and returned in a ReturnMessage object:

Reaching the formatter sinks results in exceptions being returned by serialized objects, possibly containing ObjRef instances.

Known Suspects

We have observed or suspect that the following libraries use the LogicalCallContext to store a MarshalByRefObject (most often a ObjectHandle), that leaks an ObjRef:

The first one is also used if you add Application Insights to an ASP.NET web application for .NET Framework 4.5.x or if you enable Application Insights in the Monitoring tab during creation of an Azure App Service Web Application with ASP.NET 3.5 or 4.8 stack.

Exploitation

Note that with default configuration the deserialization in the SoapServerFormatterSink/BinaryServerFormatterSink happens under Code Access Security (CAS) restrictions with TypeFilterLevel.Low. That means no direct remote code execution.

But it is possible to use another bypass trick and an indirection to finally gain remote code execution:

Using a leaked ObjRef to exploit the deserialization in .NET Remoting via HTTP (with LateHttpHeaderParsing enabled)

We won’t elaborate on that here.

The Patch

The security updates in January 2024 changed the default behavior of parsing HTTP headers in HttpHandlerTransportSink.HandleRequest(HttpContext), and no longer allow overwriting of trusted values from HttpRequest with untrusted values from HTTP headers (AppSettings.LateHttpHeaderParsing defaults to false):

diff --git a/System.Runtime.Remoting/Runtime/Remoting/Channels/Http/HttpHandlerTransportSink.cs b/System.Runtime.Remoting/Runtime/Remoting/Channels/Http/HttpHandlerTransportSink.cs
index 97738b7..3c112ba 100644
--- a/System.Runtime.Remoting/Runtime/Remoting/Channels/Http/HttpHandlerTransportSink.cs
+++ b/System.Runtime.Remoting/Runtime/Remoting/Channels/Http/HttpHandlerTransportSink.cs
@@ -4,6 +4,7 @@ using System.Collections.Specialized;
 using System.Globalization;
 using System.IO;
 using System.Net;
+using System.Runtime.Remoting.Configuration;
 using System.Runtime.Remoting.Messaging;
 using System.Web;
 
@@ -21,15 +22,24 @@ namespace System.Runtime.Remoting.Channels.Http
 			HttpRequest request = context.Request;
 			HttpResponse response = context.Response;
 			BaseTransportHeaders baseTransportHeaders = new BaseTransportHeaders();
-			baseTransportHeaders["__RequestVerb"] = request.HttpMethod;
-			baseTransportHeaders["__CustomErrorsEnabled"] = HttpRemotingHandler.CustomErrorsEnabled(context);
-			baseTransportHeaders.RequestUri = (string)context.Items["__requestUri"];
+			if (AppSettings.LateHttpHeaderParsing)
+			{
+				baseTransportHeaders["__RequestVerb"] = request.HttpMethod;
+				baseTransportHeaders["__CustomErrorsEnabled"] = HttpRemotingHandler.CustomErrorsEnabled(context);
+				baseTransportHeaders.RequestUri = (string)context.Items["__requestUri"];
+			}
 			NameValueCollection headers = request.Headers;
 			foreach (string text in headers.AllKeys)
 			{
 				string value = headers[text];
 				baseTransportHeaders[text] = value;
 			}
+			if (!AppSettings.LateHttpHeaderParsing)
+			{
+				baseTransportHeaders["__RequestVerb"] = request.HttpMethod;
+				baseTransportHeaders["__CustomErrorsEnabled"] = HttpRemotingHandler.CustomErrorsEnabled(context);
+				baseTransportHeaders.RequestUri = (string)context.Items["__requestUri"];
+			}
 			baseTransportHeaders.IPAddress = IPAddress.Parse(request.UserHostAddress);
 			Stream inputStream = request.InputStream;
 			ServerChannelSinkStack serverChannelSinkStack = new ServerChannelSinkStack();

You can make you web app vulnerable again by setting the microsoft:Remoting:LateHttpHeaderParsing app setting to true, for example, in the Azure Console:

%SystemRoot%\System32\inetsrv\appcmd.exe set config "%WEBSITE_SITE_NAME%" /apphostconfig:"%APP_POOL_CONFIG%" /section:appSettings /+"[key='microsoft:Remoting:LateHttpHeaderParsing',value='true']"

Timeline

  • 2023-11-03: Report was filed in MSRC Researcher Portal.

  • 2023-11-03: Report status changed from New to Review / Repro.

  • 2023-12-14: CODE WHITE added a note to the case that Azure App Services were also found to be affected by default with step-by-step instructions on how to create a vulnerable Web App instance using the ASP.NET 4.8 runtime stack.

  • 2023-12-28: MSRC closed the case with the following remark:

    […] after careful investigation, we determined this case does not meet our bar for immediate servicing.

    This was followed by a usage recommendation for .NET Remoting:

    .NET Remoting is a legacy product that is supported only for backward compatibility. It is not recommended for use across mixed-trust environments because it cannot maintain the separate trust levels between client and server. For example, you should never expose a .NET Remoting endpoint to the Internet or to untrusted clients. We recommend existing Remoting applications be migrated to newer technologies.

  • 2024-01-09: Microsoft released the security updates for January 2024, mentioning the following as security improvement (e. g., in KB5033911):

    .NET Framework Remote Code Execution Vulnerability
    This security update addresses a remote code execution vulnerability to HTTP .NET remoting server channel chain.

    The build timestamp of the affected library System.Runtime.Remoting.dll is 2023-11-30.

This concludes the case. No CVE was assigned, nor was there any acknowledgment.

Update

  • 2024-03-22: After reassessment, Microsoft published CVE-2024-29059:

    This CVE was addressed by updates that were released in January 2024, but the CVE was inadvertently omitted from the January 2024 Security Updates.

We have also released the GitHub repo HttpRemotingObjRefLeak with additional resources such as an vulnerable web app, some example exploit payloads and a script for automated ObjRef leaking and payload delivery.