ASP.net ViewState introduction
Whenever we encounter applications using ASP.net technology, ViewState Deserialization always come to mind as it is an inherent flaw in the technology itself. However, this technique cannot be used against the application that we are testing as we need to get hold of the decryption keys and validation keys found in web.config. Usually in a remote attack scenario, it will require a directory traversal vulnerability or local file inclusion vulnerability to disclose the machine keys. Even having these vulnerabilities might not guarantee success since system administrators might have adopt best practices to encrypt the machine key section of the web.config.
A brief overview on ViewState can be found in  and Sanjay has done a great amount of work summarizing the different types of ViewState that would be encountered based on the settings on the remote web application. The tool “Blacklist3r” mentioned relies on searching for possible pre-shared machine keys and is in essence a brute forcing tool to verify whether the provided machine key is able to decrypt the encrypted Viewstate which unfortunately does not work for our case.
Swapneil Kumar Dash  has experimented in detail on the steps on to achieve RCE using ViewState deserialization and the different conditions to achieve the end goal. If machine keys are known in clear without any modifiers to the attackers, it is very clear-cut that the next step is to use the infamous “ysoserial.net” tool to generate the payload to obtain remote code execution. However, in scenarios whereby the machine keys are known but modifiers such as “IsolateApps” and “IsolateByAppId” are added, the attacker would need to know how to derive the unique encrypted key for each application.
This article will cover how to derive the actual machine keys to achieve RCE when the “IsolateApps” modifier is used. It will mainly be documenting our journey in deriving the generated keys during one of the engagements. According to the official documentation , “IsolateApps” will generate a unique encrypted key using the application’s AppDomainAppVirtualPath which is the virtual path of the application. For example, if the virtual path for the application is set to “/app1”, ASP will use “/app1” to generate a hash (and yes, the hash is using non-standard algorithm) followed by modification of the first 4 bytes of the validation key and decryption key based on the hash.
In the engagement, we encountered an unencrypted web.config through a directory path traversal vulnerability which has “IsolateApps” modifier on the machine keys. Based on initial search, we are unable to find much information or tools that can help us derive the generated key. To improve our understanding, we used dnspy to attempt to understand the inner working on how the key is generated so that we can achieve our objective.
Understanding deeper on how validation key and decryption key are derived
Since ASP.net is a technology built on top of the .Net framework, we will need to narrow down the code to read since .Net framework is huge. Googling “viewstate deserialize c# msdn” give us a hit on the ObjectStateFormatter Class which is in the System.Web.dll and therefore we will start from there. Before reading the source code, we will need to set up an IIS instance and ensure that the machine key has the “IsolateApps” modifier in the web.config. Since setting up of IIS is easily googleable, we will not cover it here.
After browsing through the code in the ObjectStateFormatter Class, we found several Deserialize function. As dnspy has debugging capability, we will make full use of it to know which deserialize function is being called. We placed a breakpoint on the Deserialize functions and attach the debugger on w3wp.exe. If you do not find a w3wp.exe process, you will have to visit the application once. Once we visit the application, we will observe that the one of the breakpoints will be hit confirming that our ViewState has to undergo that particular function in order to be deserialized.
If we read the Deserialize function in detail, we will be able to observe that the function MachineKeySection.EncryptOrDecryptData is called which is interesting since it aligns with what we want to find out so we will jump into that function to see whether we can find out more on how the encryption or decryption is done. We note that our main aim here is to determine how to extract the validation key and decryption key so that we won’t get drifted away reading tons of code.
Diving into EncryptOrDecryptData, we will notice that EnsureConfig will be called to ensure that the MachineKeySection class is properly initialized. A naïve method will be to place a breakpoint at this method to extract out the machine keys in memory.
When the breakpoint is hit and upon inspecting the memory using watch window, we noticed that the ValidationKey and DecryptionKey is extracted directly from the web.config and _ValidationKey and _DecryptionKey is zeroed out. This suggests that the actual keys are actually initialized in _ValidationKey and _DecryptionKey and are passed somewhere before being zeroed out at DestroyKeys function.
Keys, keys… where are they?
To score a quick win to boost our motivation, finding out what the validationkey and decryptionkey are before they are destroyed would be the easiest way to extract the derived keys. It turns out that we are unable to put a breakpoint before they are destroyed because w3wp.exe is spawned with random command line arguments, and hence we are unable to start a new w3wp.exe under the debugger. In order to have a w3wp.exe to be spawned, we will need to visit the application and this action would cause the keys to be initialized and then destroyed before we can attach the debugger to the w3wp.exe process.
One trick to solve this problem is to change the web.config and save it. As IIS will attempt to restart the app pool once there is a change to the web.config. When we browse to our application again after making the change to web.config, we will observe that our breakpoint at the DestroyKeys function will be hit and then from there, we will be able to read the derived validation key and decryption key.
Another method to extract the keys is to look around and see where the _Validationkey and _Decryptionkey are being passed before being destroyed. We observed ValidationKeyInternal and DecryptionKeyInternal are being cloned from _validationkey and _decryptionkey respectively but are zeroed out when we tried to inspect them.
Using ValidationKeyInternal and DecryptionKeyInternal, we are able to narrow down to ConfigureEncryptionObject function where the two variables are used to set the s_validation key and initialize the cryptographic algorithm’s key.
For the case of decryption key, we observe that the decryption key is set in symAlgo.Key field and we know that the symAlgo is a static member of MachineKeySection represented by s_oSymAlgoDecryption.
Algorithm to derive machine key for IsolateApps
By now we know how to extract the actual validation keys and decryption keys, but we still do not know what are the factors that contribute to the manipulation of the original keys in web.config. In some of the function, we noticed that there are checks to ensure that DataInitialized must be true. Going by this logic, we attempt to find which function set DataInitialized to be true and found the function to be RuntimeDataInitialized function. As part of the function would check whether the validation key and decryption key end with “IsolateByAppId” and “IsolateApps”, we are sure that the derivation of the actual keys would reside in this function.
From the sequence of code shown, the algorithm to generate the actual decryption key and validation key is to obtain a hash of the AppDomainAppVirtualPath (as stated in MSDN) and replace the first four bytes of the original decryption key and validation key. To get the AppDomainAppVirtualPath, we can make use of the applicationHost.config which contains the IIS settings at C:\Windows\System32\inetsrv\config\. For example, in the applicationHost.config below, the AppDomainAppVirtualPath would be “/” since no virtual path has been set.
With the virtual path and the original machine keys known, we have two methods to obtain the actual keys that are residing on the remote server. We could either derive the keys based on the algorithm or set up an exact replica on the local IIS using the settings in applicationHost.config and machine keys and then extract the keys based on above mentioned methodology.
Once we have extracted the derived keys, we wrote a short ASP.net application (see sample output below) replicating the EncyrptOrDecryptData function. Our ASP.net application will be using the derived keys (make sure there is no “Isolateapps” modifier) to verify that the derived decryption key is correct. Being able to perform a decryption of an actual ViewState obtained from the remote application by confirming that the magic bytes of the ViewState starts with 0xFF 0x1 confirms that the decryption key is correct. From there, we can proceed to use ysoserial.net tool to generate the payloads that we want.
To achieve RCE on an ASP application with “IsolateApps” or “IsolateByAppId” modifier, we would require the unencrypted original machine keys and the settings in applicationHost.config. If the settings in applicationHost.config are not known, we can make clever guesses based on the URL of the application for applications with “IsolateApps” modifier. For “IsolateByAppId”, the input to be hashed is predictable and therefore, we leave it as an exercise for the reader to experiment.
Another condition that was not considered is what if the application use auto-generated keys. Based on , we would need a method to read the registry key ‘HKEY_CURRENT_USER\Software\Microsoft\ASP.NET\4.0.30319.0\AutoGenKeyV4’ or ‘HKEY_CURRENT_USER\Software\Microsoft\ASP.NET\2.0.50727.0\AutoGenKey’. In order to read these values, the attacker will most likely already have RCE and therefore there is no need for them to extract the auto generated keys to get RCE.
It is only when we are researching more in order to write this article that we tumble upon  that states how the validation key and decryption key are manipulated. 🤷♂️ However, we do hope that with this documentation, it will help in someone’s progress in any engagement.