Monthly Archives: May 2019

Danger of Stealing Auto Generated .NET Machine Keys

In the Exploiting Deserialisation in ASP.NET via ViewState blog post, I explained how it is possible to run code on an ASP.NET web application using compromised Machine Key secrets. It covers cases in which the keys are hard coded and could be read using another vulnerability such as local file disclosure. However, most websites do not hard code these keys and use automatically generated values by ASP.NET. As a result, it is not simply possible to steal the keys by reading the configuration files.

Now I want to explain how hackers who have already exploited an ASP.NET application can read the auto generated parameters to maintain their access even after their original vulnerability has been patched.

This can also be abused very similar to a hidden key or a backdoor by malicious developers to execute code on a server when they do not have their access anymore.

What do you do with a comprised box?

Assume this generic scenario: An outdated ASP.NET CMS on IIS has been hacked using a vulnerability that has been patched in the latest version of the software.

This is what we know:

  • The exploit and its patch are public
  • A web shell was dropped
  • No other file changes have been recorded
  • Database values have not been modified by the attacker
  • No fixed machine key is in the web.config file

This is what we have done after pulling the server offline:

  • The web server has been restored to its pre-attack condition:
    • Web shell has been deleted
  • Database has been restored to its pre-attack condition
    • Any potential data changes have been reversed
  • The CMS application has been updated
    • Vulnerability has been patched
  • All the hardcoded secrets and credentials within the application files or the database have been changed
  • Users’ passwords and other sensitive tokens in the accessible databases have been reset

Can we go online now without risk of attackers coming back?

Although some people may say “NO” for other reasons, I like to say “NO” as the auto generated Machine Key might have been stolen! This can be enough for hackers to run code on the server whenever they want.

Reading the Generated Keys

Attackers should be able to calculate the validation and decryption keys by reading the following registry keys (depends on the .NET version):

HKEY_CURRENT_USER\Software\Microsoft\ASP.NET\4.0.30319.0\AutoGenKeyV4   

HKEY_CURRENT_USER\Software\Microsoft\ASP.NET\ 2.0.50727.0\AutoGenKey  

These registry keys belong to the IIS user (Application Pool) that runs the application.

However, when attackers can run ASP.NET code on the website, it easier to extract the keys directly. It should be noted that used keys where ASP.NET Framework 4.5 has been targeted are different than the previous versions.

Auto generated keys can have two modes:

  • IsolateApps
    • In this case, it uses the value of HttpRuntime.AppDomainAppVirtualPath (e.g. /dir/appname/) when transforming the auto-generated key to make the validation and decryption keys
  • IsolateByAppId
    • In this case, it uses the value of HttpRuntime.AppDomainAppId (e.g. /LM/W3SVC/1/ROOT/dir/appname/ where ‘1’ is the AppId) when transforming the auto-generated key to make the validation and decryption keys. This is useful when two different applications use the same virtual path so their keys will be different.

Different ASP.NET pages within the same application uses the same set of transformed keys.

I have created a simple proof of concept code in the following GitHub gist in order to extract auto generated validation and decryption keys:

https://gist.github.com/irsdl/36e78f62b98f879ba36f72ce4fda73ab

After extracting the keys, attackers can run code on the website using the method explained in the Exploiting Deserialisation in ASP.NET via ViewState blog post.

Recommendation:

Create a new Application Pool for your ASP.NET application when you need to reset all the credentials for any reasons. This will ensure that a new ASP.NET key will be generated for the application.

References:

https://gyorgybalassy.wordpress.com/2013/12/07/how-unique-is-your-machine-key/
https://devblogs.microsoft.com/aspnet/cryptographic-improvements-in-asp-net-4-5-pt-1/
https://devblogs.microsoft.com/aspnet/cryptographic-improvements-in-asp-net-4-5-pt-2/

x-up-devcap-post-charset Header in ASP.NET to Bypass WAFs Again!

In the past, I showed how the request encoding technique can be abused to bypass web application firewalls (WAFs). The generic WAF solution to stop this technique has been implemented by only allowing whitelisted charset via the Content-Type header or by blocking certain encoding charsets. Although WAF protection mechanisms can normally be bypassed by changing the headers slightly, I have also found a new header in ASP.NET that can hold the charset value which should bypass any existing protection mechanism using the Content-Type header.

Let me introduce to you, the one and only, the x-up-devcap-post-charset header that can be used like this:

POST /test/a.aspx?%C8%85%93%93%96%E6%96%99%93%84= HTTP/1.1
Host: target
User-Agent: UP foobar
Content-Type: application/x-www-form-urlencoded
x-up-devcap-post-charset: ibm500
Content-Length: 40
 
%89%95%97%A4%A3%F1=%A7%A7%A7%A7%A7%A7%A7

As it is shown above, the Content-Type header does not have the charset directive and the x-up-devcap-post-charset header holds the encoding’s charset instead. In order to tell ASP.NET to use this new header, the User-Agent header should start with UP!

The parameters in the above request were create by the Burp Suite HTTP Smuggler, and this request is equal to:

POST /testme87/a.aspx?HelloWorld= HTTP/1.1
Host: target
User-Agent: UP foobar
Content-Type: application/x-www-form-urlencoded
Content-Length: 14

input1=xxxxxxx

I found this header whilst I was looking for something else inside the ASP.NET Framework. Here is how ASP.NET reads the content encoding before it looks at the charset directive in the Content-Type header:

https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System/net/System/Net/HttpListenerRequest.cs#L362

if (UserAgent!=null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) {
	string postDataCharset = Headers["x-up-devcap-post-charset"];
	if (postDataCharset!=null && postDataCharset.Length>0) {
		try {
			return Encoding.GetEncoding(postDataCharset);

Or

https://github.com/Microsoft/referencesource/blob/08b84d13e81cfdbd769a557b368539aac6a9cb30/System.Web/HttpRequest.cs#L905

if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) {
	String postDataCharset = Headers["x-up-devcap-post-charset"];
	if (!String.IsNullOrEmpty(postDataCharset)) {
		try {
			return Encoding.GetEncoding(postDataCharset);

I should admit that the original technique still works on most of the WAFs out there as they have not taken the request encoding bypass technique seriously ;) However, the OWASP ModSecurity Core Rule Set (CRS) quickly created a simple rule for it at the time which they are going to improve in the future. Therefore, I disclosed this new header to Christian Folini (@ChrFolini) from CRS to create another useful rule before releasing this blog post. The pull request for the new rule is pending at https://github.com/SpiderLabs/owasp-modsecurity-crs/pull/1392.

References:
https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/august/request-encoding-to-bypass-web-application-firewalls/
https://www.slideshare.net/SoroushDalili/waf-bypass-techniques-using-http-standard-and-web-servers-behaviour
https://soroush.secproject.com/blog/2018/08/waf-bypass-techniques-using-http-standard-and-web-servers-behaviour/
https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/september/rare-aspnet-request-validation-bypass-using-request-encoding/
https://github.com/nccgroup/BurpSuiteHTTPSmuggler/