1.什么是ViewState
既然是研究ViewState的问题,我们还是先抱着开发者的心态,简单地了解一下什么是ViewState,以及它是用来解决什么问题的。
首先,我敢打包票,读者在日常渗透测试,尤其是对.NET站点的测试中,一定遇到过下面这样的东西:
响应里可能有一个__VIEWSTATE,不只是响应,你在请求里肯定也会遇到,并且这个参数往往巨长无比,里面肯定包含了什么信息,你肯定会好奇,这到底是什么鬼东西
从名字看来,这就是我们说的viewstate了,那么它到底是干什么的呢?在回答这个问题之前,我们再抛出一个问题,假设我们想让页面持久的保存我们的信息,比方说用户输入一个字符,我们可以把它保存在页面上,继续输入,继续保存。在不使用数据库的情况下,你会怎么做?相信各位师傅都有各种乱七八糟的想法,而在ASP.NET中,viewstate就是为了解决这个问题的。用比较正式一点的说法:
ViewState是ASP.NET中的一种机制,主要用于在页面回发(postback)时保持页面的状态。它可以存储用户输入的数据、控件的状态等信息,以便在页面生命周期的不同阶段(例如在服务器端处理后)能够恢复这些状态。具体来说,ViewState会将这些信息序列化为一个隐藏字段,嵌入在HTML中,随页面一起发送到客户端。
我们尝试写一个ViewStateDemo.aspx文件
其指向的.NET代码内容如下:
可以看到,我们似乎是把用户输入保存在了一个名为ViewState的特殊变量里,我们是给这个变量里的TextBoxValue进行赋值的,现在,我们可以通过触发这个按钮,来给ViewState里存值,同样的,页面加载时也会从ViewState里取值
当然,这样的演示可能还不够直观,我们可以换个demo
我们不断点击这个按钮
会一直发包触发按钮事件,并且我们能看到num在一直增加,证明这个状态被我们保存了。
你可能会感到很无语,并且对这个ViewState机制感到意义不明,没关系,我也一样。我们只需要记住一点,就是当目标启用ViewState机制的时候,请求和响应里都会有一个大大的__VIEWSTATE就够了(实际上就算没有这个,也可能启用了ViewState机制,后面再说吧)。
那么这又和安全有什么关系呢?
2.ViewState在不同配置、不同.NET版本下的安全问题
感觉ViewState在实战中遇到很多,因为相比于其它.NET反序列化漏洞,这玩意实在是太过常见,而且无论是黑盒还是白盒都有操作空间,所以感觉还是很有价值的,打算好好学习和分享一下。既然和实战有关联,所以这篇文章不会一上来就分析ViewState的流程。秉持着先问是不是,再问为什么的思想。还是先学习一下实战里各种环境下要怎么打,后面再针对每个情况进行调试分析,无论是想学操作的师傅还是想了解原理的师傅都可以放心阅读。
①关闭mac验证且关闭加密的情况
(enableViewStateMac为false且viewStateEncryptionMode为Never)
这个点还是很坑的,实战里估计挺罕见的。在.NET 4.5之前,用户可以自己选择要不要开启enableViewStateMac验证,体现在web.config里的pages标签的enableViewStateMac配置项:
<pages
enableViewStateMac="false"
/>
这样就是关闭Mac验证
(注:由于4.5和4.0之间没有乱七八糟的版本号,因此4.5版本之前其实就大致约等于4.0左右的版本)
然而,从.NET Framework 4.5.2 开始强制启用 ViewStateMac 功能,也就是说你用户这个enableViewStateMac配置与否,都不影响,默认开启。虽然下文IIS使用.NET Framework 4.0,但是依然受到这个的影响,用正常的方法无法将enableViewStateMac修改为false,怀疑和新版本的IIS有关。
要改的话有两种办法,第一种是改注册表:
把这个AspNetEnforceViewStateMac改成0,但是我实测这个办法不好用,当IIS检测到你在没有web.config文件,或者web.config文件没有配置machineKey的情况下使用ViewState功能,无论如何都会自动生成machineKey,并且启用mac验证和加密配置
另一个办法是在配置文件里添加另一个配置项:
<appSettings>
<add key="aspnet:AllowInsecureDeserialization" value="true" />
</appSettings>
我实测添加配置项的办法要好用,总之,只要没有启用mac验证,且viewStateEncryptionMode为Never,是最危险的,你有没有配置machineKey都不影响后续利用,这里可以非常简单的去利用,在这种情况下,只需要用ysoserial.net去生成一个LosFormatter的链子就可以打了。这里还有一个值得注意的坑点是,我们的命令最好是这种把字符串写入txt里的命令,方便我们验证,很多人(包括我)在这里喜欢用calc.exe弹计算器,但是这里很奇葩的是计算器弹不出来,不知道什么情况,但是命令其实是能成功执行的。
总之只需要生成一个LosFormatter的payload,对其进行URL编码,然后放到__VIEWSTATE参数的位置并发包:
会得到一个“此页的状态信息无效,可能已损坏”的错误信息,查看其报错信息堆栈
可以看到ObjectDataProvider被调用,对应目录下也成功生成了文件,说明命令成功执行
这里很有意思的一点是,为什么在这种情况下能直接用LosFormatter的payload?结合之前学习过的LosFormatter、ObjectStateFormatter反序列化问题,我们可以有一些猜想了,后续再来分析。
②关闭mac验证且开启加密的情况
(enableViewStateMac为false且viewStateEncryptionMode为Auto或Always)
还是.NET FrameWork 4.0,还是关闭mac验证,但是把viewStateEncryptionMode配置为Auto或者Always
此时还是回到页面上调用功能,发现除了__VIEWSTATE和__VIEWSTATEGENERATOR传参,还多了一个__VIEWSTATEENCRYPTED传参,并且发现我们__VIEWSTATE的内容和之前明显不一样了,看起来是被加密过了
此时,还是使用上文的打法,生成LosFormatter的payload,然后替换__VIEWSTATE内容,发现失败
并且命令没有成功执行。这种情况下我们就猜测是对__VIEWSTATE的内容进行了加解密,我们要如何破局呢?很简单,这里只要把__VIEWSTATEENCRYPTED=删除掉就行
删掉这个参数之后,后端就不会对你传入的内容进行解密流程,因此删掉之后再发包,可以看到payload又成功执行
可见在这种情况下,后端对__VIEWSTATE内容是否解密,仅取决于__VIEWSTATEENCRYPTED参数是否存在,若不存在,就不进行解密了,而这玩意是用户可控的,因此,关闭mac验证但开启加密功能,也没用!删掉__VIEWSTATEENCRYPTED就可以按照之前的打法去打了。
③开启mac验证且关闭加密的情况
(enableViewStateMac为true且viewStateEncryptionMode为Never)
依旧是.NET FrameWork 4.0,一旦开启mac验证,事情就变得复杂起来了。首先介绍一下enableViewStateMac为true且viewStateEncryptionMode为Never的情况吧:
把这个配置改为true,此时,再使用之前的LosFormatter的payload,提示如下:
“验证视图状态 MAC 失败。如果此应用程序由网络场或群集托管,请确保 <machineKey> 配置指定了相同的 validationKey 和验证算法。不能在群集中使用 AutoGenerate。”
此时__VIEWSTATE需要验证mac,在这种情况下,我们至少需要知道两个信息:
首先是validation和validationKey,validation默认是SHA1,并且也只有
SHA1|MD5|3DES|AES|HMACSHA256/384/512
这几种,关键在validationKey我们得获取到
除此之外,我们还需要获取__VIEWSTATEGENERATOR或apppath和path,我们先介绍获取到了__VIEWSTATEGENERATOR的情况:
如图,在这种情况下,我们有__VIEWSTATEGENERATOR,并且已知validation和validationKey,要如何利用?
使用ysoserial.net去生成payload利用即可:
ysoserial.exe -p Viewstate -g TextFormattingRunProperties -c "cmd.exe /c echo justatest > C:\Windows\temp\test.txt" --generator=D4124C05 --validationalg="SHA1" --validationkey="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
或
ysoserial.exe -p Viewstate -g TextFormattingRunProperties -c "cmd.exe /c echo justatest > C:\Windows\temp\test.txt" --generator=D4124C05 --validationalg="SHA1" --validationkey="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" --islegacy
关键部分主要就是generator和validationkey,照着填好信息即可
使用新生成的payload替换__VIEWSTATE内容并发包,看到了熟悉的“此页的状态信息无效”
再次RCE。
但是,假如目标网站没有__VIEWSTATEGENERATOR这个参数,我们获取不到它的值的情况下,该怎么利用呢?这时候需要寻找目标网站的apppath和path,这个有点抽象,比方说我下面这个路径,ASPROOT1,然后我所有aspx文件都部署在这个路径,apppath就是单独的一个/ (所谓的虚拟路径),而path就是要测试的aspx文件名,比如我们上面都是对hello.aspx进行测试,那就写成/hello.aspx
这时候使用下面的语句生成payload:
ysoserial.exe -p Viewstate -g TextFormattingRunProperties -c "cmd.exe /c echo justatest > C:\Windows\temp\test.txt" --apppath="/" --path="/hello.aspx" --validationalg="SHA1" --validationkey="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" --isdebug --islegacy
此时,ysoserial.net会自动帮我们计算出__VIEWSTATEGENERATOR的值和__VIEWSTATE的payload
我们在数据包里补充好__VIEWSTATEGENERATOR和__VIEWSTATE的值即可利用
再多来一点情况,如果再搞个test目录来放hello.aspx呢?
由于当前Apppath依然是/,但是path改变了,因此要用--apppath="/" --path="/test/hello.aspx"去生成
那么什么情况下Apppath才会改变呢?答案是在ASPROOT1下再加一个应用程序,ASPROOT1在/ 我们再添加一个/test应用程序,物理路径指定为E:\root\ASPROOT1\test
此时Apppath是/test path是/test/hello.aspx
有点烧脑,实战里在获取不到__VIEWSTATEGENERATOR的情况下,要找Apppath和path估计会很恶心,除非依靠报错泄露绝对路径,那我们可能可以逐层截取路径排列组合Apppath和path
④开启mac验证且开启加密的情况
(enableViewStateMac为true且viewStateEncryptionMode为Auto或Always)
依然是.NET FrameWork 4.0
在这种情况下,我们熟悉的__VIEWSTATEENCRYPTED又回来啦!
怎么办呢?还是老办法,给__VIEWSTATEENCRYPTED删了,这样我们不需要decryption和decryptionKey,已知validation和validationKey以及__VIEWSTATEGENERATOR即可利用
ysoserial.exe -p Viewstate -g TextFormattingRunProperties -c "cmd.exe /c echo justatest > C:\Windows\temp\test.txt" --generator=D4124C05 --validationalg="SHA1" --validationkey="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
所以,在.NET 4.0版本的时候,这个加密其实完全就是个局外人,有他没他一个样,我们主要关心validationkey和__VIEWSTATEGENERATOR。当我们掌握目标的任意一个__VIEWSTATE数据,并且掌握__VIEWSTATEGENERATOR(或Apppath+path)的时候,还可以爆破validationkey,使用这两个项目其中一个(推荐前者):
https://github.com/NotSoSecure/Blacklist3r
https://github.com/blacklanternsecurity/badsecrets
它的使用场景也很简单,例如,我们在实战中碰到了使用VIEWSTATE的页面
拿到了一个VIEWSTATE
/wEPDwUKMjA3NjE4MDczNmRkN32OCJGGLs18e+kIYrNLDup+I4c=
以及一个__VIEWSTATEGENERATOR
D4124C05
然后用Blacklist3r填好参数导入字典爆破就行。
在我们的字典里加一条数据,逗号前面是validationKey,后面是decryptionKey。
在上面这种无需关心decryptionKey的场景下爆破validationKey,也就不用管后面的decryptionKey,但是一定要有decryptionKey,不然工具会报错导致跑不了。使用下面的语句对VIEWSTATE测试:
AspDotNetWrapper.exe --keypath MachineKeys.txt --encrypteddata /wEPDwUKMjA3NjE4MDczNmRkAsmDbPuTPRrgKqppg/pAeSRtnbLT1sSS544sbiy46BY= --purpose=viewstate --modifier=D4124C05 --macdecode
这里可以看看到ValidationKey被成功找到了(DecryptionKey就算乱填这里也还会成功,因为这个场景只看ValidationKey)
然后会生成一个DecryptedText文本文件,里面记载着VIEWSTATE的信息
⑤关闭mac验证且关闭加密(.NET Framework 4.5及以上)
注意这里appSettings里的配置也是必须的,否则不管你怎么配置都没办法关掉mac验证和加密,.NET 4.5以上强制开启。
Web.config里添加上述配置,然后同样地同时关闭enableViewStateMac和enableEventValidation
此时,和我们最上面介绍的情况一样,用LosFormatter的payload去打即可。
⑥mac验证或加密开启其一或都开启(.NET Framework 4.5以上)
在.NET Framework 4.5以上,mac验证或加密开启其一或都开启的效果是一样的,无论你怎么配置,只要打开其中的一个,那么就相当于全部开启,也即:
关闭mac验证,开启加密
开启mac验证,关闭加密
上述两种情况其实都等价于同时开启mac验证和加密
此外,在这种情况下,你可能还能见到__VIEWSTATEENCRYPTED这个象征加密的参数,但是此时,删掉它也不影响后端是否进行加解密,加解密默认进行。因此无法用之前将其删除的手段规避加解密。
同时,在这种情况下,__VIEWSTATEGENERATOR无效,想要进行ViewState利用,必须拿到apppath和path。
总结一下,在这种情况下,必须要满足如下条件才可以利用:
获取decryption和decryptionKey(无法通过删除__VIEWSTATEENCRYPTED来规避加解密流程)
获取validation和validationKey
获取apppath和path(无法通过__VIEWSTATEGENERATOR生成payload)
使用如下语句生成payload:
ysoserial.exe -p Viewstate -g TextFormattingRunProperties -c "cmd.exe /c echo justatest > C:\Windows\temp\test.txt" --path="/hello.aspx" --apppath="/" --validationalg="SHA1" --decryptionalg="AES" --decryptionkey="0123456789abcdef0123456789abcdef" --validationkey="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
也即在这种情况下,必须要指定path和apppath,必须要指定加密类型和加密密钥,否则无效
Payload丢到__VIEWSTATE里,至于数据包里如果有__VIEWSTATEENCRYPTED和__VIEWSTATEGENERATOR,看你心情,可要可不要,不影响。
再度RCE。
同理,在这种情况下可以借助上面提到的工具进行decryptionKey和validationKey的爆破:
AspDotNetWrapper.exe --keypath MachineKeys.txt --encrypteddata DrBq3l0Yri3pNrb+hdBR2DmO0nREmjdBV3hJVPhchPtpfYOqhlLkRTIpFr4y7XW8c2Em6TdScfveer+M/N2p786TykO5U7/YBarjmv/LHo8= --decrypt --purpose=viewstate --IISDirPath "/" --TargetPagePath "/hello.aspx"
和我们上面介绍的一样,这时候还要额外提供apppath和path,另外这时候DecryptionKey就不是局外人了,得确保它正确才行
结束后也会生成一个记载VIEWSTATE信息的文件
⑦开启ViewStateUserKeys配置
开发者可以在aspx文件的Page_Init()方法中进行ViewStateUserKey的初始化配置,如下图:
注意这里使用了ViewStateUserKey,其值为anothertest,会对我们的利用造成影响,这里我使用.NET 4.5以上且开启mac验证和加密的环境。
书接上文,用普通的配置了decryptionKey、validationKey、apppath、path,但没配置ViewStateUserKey的payload进行测试:
此时失败,解决方法也简单,我们生成payload的时候加一个
--viewstateuserkey="anothertest"
参数,使用的语句如下:
ysoserial.exe -p Viewstate -g TextFormattingRunProperties -c "cmd.exe /c echo justatest > C:\Windows\temp\test.txt" --path="/hello.aspx" --apppath="/" --validationalg="SHA1" --decryptionalg="AES" --decryptionkey="0123456789abcdef0123456789abcdef" --validationkey="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" --viewstateuserkey="anothertest"
这样生成出来的payload又可以使用了
总之实战中还可以多关注有没有ViewStateUserKey的配置,有的话得加上。
⑧隐藏__VIEWSTATE或关闭enableViewState
很多开发者可能以为enableViewState设置成false就是关掉__VIEWSTATE机制
或者觉得把__VIEWSTATE隐藏起来不让攻击者看到就安全了,实际上服务器还是会被动的接受解析__VIEWSTATE,因此实战中遇到ASP.NET站点,尤其是ASP.NET Web Forms站点(说人话就是aspx这样的站点),可以顺手敲一个__VIEWSTATE=AAAA看看页面变化(后续再细说这个),有时候可能发现一些隐藏起来的__VIEWSTATE机制,打开突破口。
⑨decryptionKey和validationKey启用AutoGenerate,IsolateApps模式
上面我们学习的各种情况都是开发者自己设置、硬编码了decryptionKey和validationKey,平时审计的时候也可以多注意有没有类似的问题。但在默认情况下,如果开发者没有配置machineKey标签,或者没有配置decryptionKey和validationKey的情况下,IIS实际上会自动帮我们生成一个随机的decryptionKey和validationKey:
具体到配置文件里是这样:
这种情况要怎么拿到随机的密钥呢?可以参考这篇文章里最后给出的提取密钥的aspx webshell:
https://www.cnblogs.com/zpchcbd/p/15112047.html
最后拿到的就是。看代码主要有两个部分,一个是通过反射获取Configuration里的值,但是这样在遇到自动生成的情况下没用,另一个是把生成密钥的逻辑抠出来了。
⑩自实现ViewState逻辑(重写ViewState相关方法)
重写
SavePageStateToPersistenceMedium
和
LoadPageStateFromPersistenceMedium
方法,开发者可以在代码里重写这两个方法,去实现自定义的ViewState处理逻辑,处理不当就会产生漏洞,可以参考
https://mp.weixin.qq.com/s/F3sg008W3V_jtska0YYzEA
如图,注意
LoadPageStateFromPersistenceMedium()
里的逻辑会取代原有的VIEWSTATE处理逻辑,可以看到这里拿到_MyViewState之后丢到LosFormatter里进行反序列化,因此这个自实现的ViewState逻辑是有漏洞的,我们直接生成LosFormatter反序列化点的payload传给_MyViewState参数即可
综上,我们把上面的十种情况用一图流给大家总结一下(可能还有别的情况,欢迎师傅们补充)
3.黑盒视角看ViewState问题的发现与利用
可以看到,在上述流程中,黑盒场景下最简单的、不需要任何信息就能一把梭的只有一种情况——就是没有配置Mac验证,在黑盒情况下要如何判断目标是否开启Mac验证呢?其实很简单,我们随便生成一个Base64数据放到__VIEWSTATE的位置,比方说放一个MTEx
如果出现“此页的状态信息无效,可能已损坏。”,错误堆栈为
[ArgumentException: 序列化的数据无效。]
那么大概率是没有开启mac验证,此外,当你发现请求里有__VIEWSTATEENCRYPTED参数,别忘了先把它删掉,再往__VIEWSTATE里放入base64测试数据
会得到和上面一样的报错
当然我们前面说过,页面里没看到__VIEWSTATE不一定代表目标就是不接受__VIEWSTATE,测试ASP.NET站点的时候可以手动加上试试看,说不定就有惊喜了。
除了上面这两种情况,当你看到了下面这样的错误:
验证视图状态 MAC 失败。如果此应用程序由网络场或群集托管,请确保 <machineKey> 配置指定了相同的 validationKey 和验证算法。不能在群集中使用 AutoGenerate。
那么就意味着目标至少开启了mac验证,在这种情况下,至少我们也得获取到密钥。在黑盒场景下,常见的获取web.config配置文件,或者说获取密钥的攻击路径有:
1.通过任意文件读取、下载漏洞获取
2.通过XXE等漏洞获取
3.通过目录遍历(配置不当),导致可以直接下载web.config
4.网站源码、备份文件泄露
5.多个不同站点共用同样的密钥(类似密码复用)
6.若存在硬编码问题、可下载同类型源码拿到密钥
7.Getshell后利用该手段进行权限维持(后续细说)
8.爆破弱密钥(致敬传奇鉴权组件shiro 但是真有这么好爆吗?)
4.白盒视角看ViewState问题的发现与利用
这个没什么好说的,拿到一套源码关注一下配置文件里的machineKey,是不是硬编码的,如果开发者写了自己实现machineKey生成的代码,能不能给生成逻辑扣出来伪造?
关注源码中有没有重写
LoadPageStateFromPersistenceMedium
这个方法涉及ViewState的处理逻辑,如果有页面重写了这个方法,或者有页面继承了重写了这个方法的page,那么要关注其具体的逻辑是否可利用。同理还有
SavePageStateToPersistenceMedium
负责ViewState的生成逻辑,起到类似序列化的作用,也可以多看看里面有没有可控的恶意行为。
再者,还可以多关注代码里有没有配置viewstateuserkey,毕竟这玩意也会影响ViewState的利用嘛,而且这个在黑盒场景下非常难获取,除非用文件读取漏洞读取aspx的源码。
5.红队视角看ViewState问题在权限维持场景下的应用
读者可能早就想吐槽了,你们公众号的名字是HW专项行动小组,怎么天天发SRC和安全研究的文章。那么今天就来点大家想看的东西,笔者在学习的过程中发现ViewState问题在权限维持场景下可以说是大杀器。
首先最简单的,大家都能明白的场景就不说了,如果目标的页面使用了ViewState机制,Getshell之后拿到目标的web.config信息,以及apppath、path等信息后,把这个信息保存下来,就可以用ysoserial.net的功能去生成payload进行攻击
这样就等于获得了一个无限续杯的命令执行点,在目标修改密钥前,我们都可以靠这个点进行权限维持,毕竟别人发现被日了之后,肯定急忙先把webshell排除掉,厉害一点的师傅可能还排查内存马,经验不足的师傅很难想到ViewState的密钥还能被这样利用,如果目标的ViewState密钥是随机生成的,我们也可以用上文提到的专门提取密钥的webshell进行提取
再衍生一下,如果目标的内容查杀过于厉害,一般的webshell落地不上去,我们是不是可以先落地上述提取信息的webshell,可以自己再改改,给内容再混淆混淆,毕竟这个shell只是提取信息,也没有什么一眼就很敏感的操作,相信不会对它查的那么死。先落地这个webshell,拿到密钥后,也就等于拿到一个命令执行点了。
但是这还没完,接下来才是更NB的操作,参考下面这篇文章的思路:
https://blog.wanghw.cn/security/dotnet-viewstate-no-file-godzilla-memshell.html
这个操作是什么呢?我们之前在SoapFormatter反序列化点介绍的时候,花大力气介绍了ActivitySurrogateSelector链以及几个相关链子。我们知道,借助这个特殊的选择器,可以序列化与反序列化任何一个类(即使是没有序列化特性的类),因此可以借助LINQ的委托行为去执行Assembly.Load()方法以及其它的相关方法,而这个方法又相当于JAVA里的defineClass(),因此这条链同样赋予我们动态类加载的能力。看到这里读者可能惊呼:“那我们在打ViewState的时候,可以用这个链子打.NET内存马呀”。这确实是思路之一,但接下来的操作可能比内存马还恐怖。
我是说,如果我们不打内存马,只是在利用ViewState的时候,用
ActivitySurrogateSelector
链加载哥斯拉的代码,会发生什么事?
写一个cs文件,内容是gsl的主体内容:
不用使用using引入依赖,然后我们使用如下语句来生成ViewState Payload
ysoserial.exe -p Viewstate -g ActivitySurrogateSelectorFromFile -c "gslshell.cs;System.Web.dll;System.dll" --apppath="/" --validationalg="SHA1" --decryptionalg="AES" --decryptionkey="0123456789abcdef0123456789abcdef" --validationkey="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
会生成比较大一段的payload(其实也没有很大),那我们要怎么利用呢?通过使用输出的payload进行拼接,注意最后的&不能漏掉,因为GSL的传参还得拼接在后面呢
__VIEWSTATE=<yso生成的内容>&__VIEWSTATEGENERATOR=60AF4XXX&
最后实现的效果是可以连接哥斯拉,如引用文章的图片
这相比传统的权限维持手段就很变态了,首先这并不是传统意义上的内存马,只有攻击者需要的时候,相关页面才会是一个“webshell”,普通情况下就是个正常页面,并且也没有文件落地,没有修改配置文件,防守方几乎无从排查,唯一的办法就是发现被日了之后赶快改密钥。其次,__VIEWSTATE的内容在大部分场景下本身就是加密的,没有显著特征,你可能会说上述操作会导致__VIEWSTATE内容变得很大,可是很多ASP.NET站点的__VIEWSTATE本来就很大,有时候甚至大到影响网页访问速度,因此从__VIEWSTATE大小排查也不现实。再然后,上述操作可以把恶意流量隐藏在大量正常流量中,比方说我主页index.aspx使用了ViewState机制,那么攻击者完全可以把ViewState的path指定为/index.aspx,生成针对/index.aspx的ViewState payload,并且实现哥斯拉连接的效果,这要怎么排查?是不是非常好玩?
我想到的几个对抗的手法,一个是可以用RASP相关的机制Hook住ViewState处理流程中的关键方法,在解密后拿到VIEWSTATE的内容进行恶意代码的识别与拦截。另外一个则是可以Hook ActivitySurrogateSelector链上的一些方法,检测是否有打ActivitySurrogateSelector链的恶意行为。
当然,还有一个破绽就是这里用的是哥斯拉的shell,哥斯拉的流量本身是有一些特征的,不过我们也可以通过魔改去除,后续有空可能分享一下怎么魔改哥斯拉webshell+构造流量中转脚本,在不对哥斯拉本体动刀的情况下实现流量加密自由。
6.ViewState处理流程分析(4.0版本)
也该知其所以然了,前面我们分析了这么多种情况,在代码层面究竟是什么原因呢?我们来跟一下ViewState的处理流程。首先先分析4.0版本的不同情况,4.0和4.5版本的流程是有些许不同的。
我们把mac校验、加密、ViewStateUserKey这些东西全都打开,来看看整个处理流程。通过前面的一些只是可以知道,ViewState处理流程主要在
LoadPageStateFromPersistenceMedium
方法,因此可以下一个断点。
在这里断住,可以看到处理逻辑在pageStatePersister.Load()方法里,并且此时很多变量和属性我们很熟悉:
也即所谓的apppath和path
RequestViewStateString已经拿到我们传入的__VIEWSTATE内容了
跟进Load()方法
注意到这里拿到传入的__VIEWSTATE和我们定义的ViewStateUserKey。然后传入一个
Util.DeserializeWithAssert()
方法
跟进相关方法,发现他是对传入的formatter形参调用Deserialize()方法。
这里可以跟进到ObjectStateFormatter类的Deserialize()方法,时隔几天,我们又杀回来了!我们继续看,这里把__VIEWSTATE内容先进行了一次Base64解码
接下来的分支就很有意思了
注意到第一个if()语句,这里是
AspNetCryptoServiceProvider.Instance.IsDefaultProvider && !_forceLegacyCryptography
_forceLegacyCryptography默认情况下就是false,因此后一个条件默认为true,真正影响分支的就是第一个
AspNetCryptoServiceProvider.Instance.IsDefaultProvider
在4.0及以下的.NET Framework版本中,这个变量的值为false,而4.5以上则为true,这是一个重点,由于当前是4.0版本,其值为false,所以这个地方会直接跳到else if分支(4.5的情况我们后面单独分析):
注意到这里有两个值,
_page.ContainsEncryptedViewState
和
_page.EnableViewStateMac
看名字就可以联想到他们分别和加解密流程以及Mac验证流程有关。
我们先看
_page.ContainsEncryptedViewState
其默认是一个false类型。
在page类里找到其赋值流程:
可见,当WEB请求里有__VIEWSTATEENCRYPTED传参时,就会将
ContainsEncryptedViewState
赋值为true,这会导致进入这个分支,调用EncryptOrDecryptData()方法对内容进行解密
也就是说,如果我们删掉请求里的__VIEWSTATEENCRYPTED,那么
_page.ContainsEncryptedViewState
为false就不会进入这个解密流程,这也就是我们上面介绍的删除__VIEWSTATEENCRYPTED从而规避加解密的姿势。
当然,虽然不会进去,但我还是对这个方法,尤其是GetMacKeyModifier()很感兴趣,这个方法是干什么的呢?
这个方法首先判断了一下_macKeyBytes是否为null,有的话直接返回其值。由于我们这里是null,那么进入内部的分支。这里先进入GetClientStateIdentifier()方法
GetClientStateIdentifier()
方法又要调用
GetNonRandomizedHashCode()
方法,传入TemplateSourceDirectory,再进入
这里的传参s正是我们的apppath,单个/,这个函数的逻辑也很简单,就是计算传入字符的hash
接下来,再进行了一次
GetNonRandomizedHashCode()
的调用,这次传入的是hello_aspx,可见,我们的path,也即/hello.aspx被处理成了hello_aspx
对hello_aspx在进行一次hash计算,最后,将apppath和path的哈希计算结果进行相加并返回
再接着,后续还判断是否存在viewStateUserKey,如果存在,还会把我们上面的
clientStateIdentifier
与
viewStateUserKey
做处理,然后给_macKeyBytes进行赋值。
可以看到,这个方法对apppath、path、viewStateUserKey做了处理,可以猜测我们需要提供apppath、path就是因为这个方法
然后这个解密流程其实就是把上文的_macKeyBytes做密钥,对传入的__VIEWSTATE先做一次解密
这里我其实在想,看起来进入加解密分支的话,就和签名校验分支没啥关系啊?事实证明我天真了,这个
EncryptOrDecryptData()
里面似乎还是有签名校验的。当然实战中可以通过删掉__VIEWSTATEENCRYPTED不进入这个分支。
然后,当没有开启加密或删掉__VIEWSTATEENCRYPTED的情况,进入下面这个分支:
这里有个很有意思的问题就是这个EnableViewStateMac是哪来的,其默认是赋值为true的
但是又关注到EnableViewStateMac属性,我们发现_enableViewStateMac能否成功赋值,还取决于
EnableViewStateMacRegistryHelper.EnforceViewStateMac
的值,如果这个值为false,才允许对这个值进行自由赋值(也即我们可以通过enableViewStateMac开启关闭这个配置)
关注
EnableViewStateMacRegistryHelper.EnforceViewStateMac
的赋值
可以看到,当flag为true时,上述这个变量被赋值为true,而flag的值又和
IsMacEnforcementEnabledViaRegistry()
方法执行结果有关
跟进IsMacEnforcementEnabledViaRegistry()方法
其值就是读取的注册表的值,因此我们前面说过,修改注册表可以关掉mac验证,当这里的注册表配置项不为0的时候,会导致EnforceViewStateMac为true,导致我们无法配置enableViewStateMac
此外还有一种情况,注意到这个分支,当
AppSettings.AllowInsecureDeserialization
也即web.config里的AppSettings标签的AllowInsecureDeserialization不为null时,将其值取反赋值给EnforceViewStateMac
也即这个配置项:
此时EnforceViewStateMac为false,我们可以自由配置enableViewStateMac。因此我们可以通过改注册表和加web.config配置来关闭enableViewStateMac,否则是关不了的。
话说回这个分支
关键在GetDecodedDate()方法,可以看到当Validation方式为DES或AES时,其实也是要走进EncryptOrDecryptData方法的
GetDecodedDate方法的作用借用前辈的结论就是:
1.判断签名算法是否为 3DES/AES, 调用 EncryptOrDecryptData 方法 (见下文)
2.校验签名, 即根据 _HashSize 分离原始数据和签名, 然后手动计算该数据的签名是否和原始签名一致
关于签名的生成和校验逻辑主要看GetEncodedData、GetDecodedData、EncryptOrDecryptData这几个方法,可以参考这个源码:
https://referencesource.microsoft.com/#System.Web/Configuration/MachineKeySection.cs
有注释要好看一些。这里由于涉及很多密码学知识,而我刚好又不会这块,所以没有详细分析,下面这篇两文章对ViewState的签名与校验流程分析的比较详细,可以参考阅读:
https://exp10it.io/2024/02/asp.net-viewstate-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#encryptordecryptdata
https://www.cnblogs.com/zpchcbd/p/15112047.html#:~:text=%E4%B8%8B%E9%9D%A2%E7%9A%84%E4%BB%A3%E7%A0%81%E6%98%AF%E5%9C%A8Obj
前人总结的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
所以从代码层面可以总结一下.NET 4.0环境下的各种情况。首先是同时关闭mac校验和加解密的情况
由于上述两个分支都进不去,也就不会对传入的__VIEWSTATE数据进行签名校验or加解密,直接就是原生的ObjectStateFormatter反序列化,而LosFormatter反序列化默认情况下也就是ObjectStateFormatter套了一层base64编码的皮,因此在这种情况下,我们自然可以用LosFormatter的payload去打。
再者,由于是否进入加解密流程与
this._page.ContainsEncryptedView
State的值有关,而其值是通过判断请求里是否携带__VIEWSTATEENCRYPTED来决定的,因此我们删掉__VIEWSTATEENCRYPTED可以绕过加解密流程。
最后,由于mac验签流程和apppath、path、validationKey紧密联系,因此我们只有搞到这些值,才能对传入的数据进行合法签名。这也就是为啥我们要搜集这些信息。那么接下来还有一个问题,__VIEWSTATEGENERATOR呢?上面我们都没见到它的身影,我们知道当获取到它的值的时候,可以取代apppath、path,这是为什么?
我们前面分析过,apppath和path的作用主要是在
GetClientStateIdentifier()
方法中被转化成hash,并且参与签名的生成和校验。
我们追踪__VIEWSTATEGENERATOR的生成:
会发现,这个值的生成实际上也是通过GetClientStateIdentifier()方法,只不过这里给它十六进制转换+字符串转换了一下。
因此,拿到__VIEWSTATEGENERATOR之后,实际上可以反推GetClientStateIdentifier()的结果,而我们前面介绍过,VIEWSTATE生成过程中需要这个结果的参与,因此拿到__VIEWSTATEGENERATOR或者拿到apppath和path都等于拿到GetClientStateIdentifier()的结果。可以参与ViewState的生成。
以上就是4.0版本的情况下对ViewState利用流程的一个分析,当然对ViewState签名和校验逻辑是没有分析的,其他师傅的文章写的很具体很详细了,可以参考一下,或者直接看看ysoserial.net生成VIEWSTATE数据的逻辑。
7.ViewState处理流程分析(4.5版本)
上面简单分析了一下4.0版本的,那么在4.5版本会有什么变化呢?
切回4.5版本,前面的流程不变,主要的变化还是发生在ObjectStateFormatter的Deserialize方法
可以看到,由于
AspNetCryptoServiceProvider.Instance.IsDefaultProvider
为true,直接进入了相关分支,并且一进来我们就知道为什么在4.5以上,加密或mac验证开启其一就相当于全部开启
因为这是一个or语句,只要其中有一个是true就会进入下面的分支,除非两个同时关闭。
所以跟进这个分支
这里先调用了GetSpecificPurposes()方法,把一些信息存在了list里
最后生成出来我们看看list:
发现list里保存的东西其实就是apppath、处理后的path、ViewStateUserKey,然后后续的
AppendSpecificPurposes
似乎只是对这个数据再进行了一个包装
接着进入GetCryptoService()方法,前文生成的purpose2传入这个方法:
这里会进一步进入
GetNetFXCryptoService(purpose, options)
方法
这个方法我就没有细跟了,总之
GetDerivedEncryptionKey()
和
GetDerivedValidationKey()
一个拿到加密密钥,一个拿到签名密钥
最后也是拿到一个对象,里面包含加密和验签密钥。后续应该也就是验签和解密逻辑。至于为什么在这个版本拿到__VIEWSTATEGENERATOR没用了,我推断是算法不同了,在这个版本里__VIEWSTATEGENERATOR还是由
GetClientStateIdentifier()
生成,但验签流程中虽然还会拿到apppath、path等信息,但是已经看不到GetClientStateIdentifier()了,因此攻击者就算拿到__VIEWSTATEGENERATOR,反推出了GetClientStateIdentifier()的执行结果,也没用,因为这玩意已经不参加签名和验签流程了?
这一点也可以从ysoserial.net的逻辑里看出来,在.net 2-4版本,需要依靠apppath、path或__VIEWSTATEGENERATOR的值去生成pageHashCode:
4.5以后变成下面这样了:
综上给出结论。当版本大于4.5时,mac验证和加密开启其一就等于同时开启,删除__VIEWSTATEENCRYPTED的方法不再有效。并且由于验签、解密逻辑的变动,获取__VIEWSTATEGENERATOR的值不再有效,必须拿到apppath和path。
有空再详细看看ViewState的加密和验签的具体逻辑(
8.总结
以上就是对ViewState在不同版本不同配置下的利用方法的总结,并且还从几个不同的视角简单研究了一下这个漏洞。不管是挖src、代码审计与安全研究、红蓝对抗,应该相对比较容易发现和利用上这个漏洞,还是非常有学习的价值的。除了ViewState自己的逻辑,其中还涉及很多反序列化点的原理以及配套的利用链,这些已经在往期学习过了,师傅们可以先阅读一下往期文章。
9.参考
最后特别感谢dot.Net安全矩阵知识星球的Ivan1ee师傅在环境配置相关问题上的指导,一开始只改注册表,但怎么都关不掉mac校验,后面才知道还可以在web.config里再加配置,明明别人改个注册表就够了(
学习这个漏洞的过程中参考了大量资料,下面的链接可能也不完全
https://www.cnblogs.com/zpchcbd/p/15112047.html //分析了4.5版本的验签和加密流程、涉及随机密钥的提取
https://exp10it.io/2024/02/asp.net-viewstate-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#getencodeddata //详细分析了.NET 4.0版本的验签和加密流程
https://soroush.me/blog/2019/04/exploiting-deserialisation-in-asp-net-via-viewstate/#google_vignette //详细介绍了不同配置、不同版本情况下ViewState的利用思路
https://book.hacktricks.xyz/pentesting-web/deserialization/exploiting-__viewstate-parameter //详细介绍了不同配置、不同版本情况下ViewState的利用思路(图文)
https://rivers.chaitin.cn/blog/cq952510lnechd244j6g //简单介绍了ViewState的利用思路
https://cyku.tw/ctf-hitcon-2018-why-so-serials/ //CTF中的ViewState利用案例
https://devco.re/blog/2020/03/11/play-with-dotnet-viewstate-exploit-and-create-fileless-webshell/ //利用ViewState实现类内存马效果
https://blog.wanghw.cn/security/dotnet-viewstate-no-file-godzilla-memshell.html //利用ViewState实现类哥斯拉内存马效果
https://github.com/Y4er/dotnet-deserialization/blob/main/ViewState.md //分析ViewState处理逻辑和常见场景的利用方法
https://mp.weixin.qq.com/s/F3sg008W3V_jtska0YYzEA //重写LoadPageStateFromPersistenceMedium的ViewState利用场景