dotnet代码审计系列(一)
dotnet代码审计系列(一)
1. 概述
1.1. 背景
.NET Framework 是微软开发的一个托管代码框架,主要用于构建和运行 Windows 应用程序。它提供了大量的类库、通用语言运行时(CLR)、以及开发和管理应用程序的工具。使用 .NET Framework 开发的应用程序通常是用 C#、VB.NET、F# 等语言编写的,编译后生成中间语言(IL),然后由 CLR 执行。
.NET Framework 的重要特性包括:
- 跨语言互操作性:不同编程语言编写的组件可以无缝协作,因为它们都编译为 IL 并在 CLR 中运行。
- 内存管理:通过垃圾收集器自动管理内存,减少内存泄漏和其他内存相关的安全问题。
- 安全模型:.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。
拿一张官方图片来描述:
2.1. WebForm开发模式
一张经典的图来描述整个过程:
- aspx页面显示,通常代码是一些html代码,第一行文件头会显示具体触发功能代码的位置,就可以通过这个位置找到处理的函数。服务器端的动作就是在aspx.cs文件中定义
- .cs是类文件,存放公共类。
- .ashx是一般处理程序,主要用于写web handler,可以理解成不会显示的aspx页面,不过效率更高
- dll就是编译之后的cs文件。
以WebGoat.NET靶场为例:
第一行的含义:
- 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
文件中定义。
目录结构:
|
|
2.2. MVC 开发模式
ASP.NET MVC 是一种基于 MVC 模式的 Web 开发框架,注重分离关注点(Separation of Concerns),即将业务逻辑、用户界面和输入控制分离。具体呈现模式和Java比较类似,在此不做赘述。
目录结构:
|
|
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 中的路由配置复杂性,适合页面数量少的应用,适用于小型项目或单页面应用程序,以及希望简化页面开发的场景。
目录结构:
|
|
3. 反编译工具
在 .NET 应用程序开发中,预编译(Precompilation)是指将应用程序的代码提前编译成DLL等库文件,以提高性能、减少首次请求的启动时间,并增强代码的保护性。预编译通常应用于 Web 应用程序,如 ASP.NET,这类应用程序的代码在部署到服务器之前可以通过预编译减少或消除运行时编译的需求。
当站点存在这种情况时,就需要反编译 /bin
目录下的 DLL 文件,来获取源码。
可选择的 .NET反编译工具主要有四个:dnSpy(于2020年左右停止更新)、ILSpy、dotPeek(JetBrains发布)、dnSpyx(非官方更新版)。
下图是 ILSpy 反编译的结果:
ILSpy对字符串搜索功能不是很友好,因此可以选择将反编译的源码导出为文本,再去审计。
如果反编译出的代码存在混淆,则可利用反混淆工具进行还原。反混淆的工具有很多,其中de4dot是目前最主流的反混淆工具。
检测混淆器类型:
|
|
批量反混淆:
|
|
4. 常见漏洞
以下漏洞代码均以WebGoat.NET靶场为例
4.1. 文件上传漏洞
/WebGoat/Content/UploadPathManipulation.aspx.cs
代码的主要目的是将用户上传的文件保存到服务器上的一个指定目录中,并在上传成功或失败时向用户显示相应的消息。
具体代码逻辑如下:
- 页面加载:
Page_Load
方法为空,没有进行任何操作。页面加载时,没有特定的逻辑执行。
- 文件上传:
btnUpload_Click
方法是按钮点击事件的处理程序,当用户点击上传按钮时触发。- 首先,代码检查
FileUpload1
控件是否包含文件(即FileUpload1.HasFile
是否为真)。 - 如果有文件上传,代码获取文件的名称,并使用
Server.MapPath
将虚拟路径转换为服务器上的物理路径。然后调用FileUpload1.SaveAs
将文件保存到指定的路径中。 - 如果文件上传成功,程序会在
labelUpload
标签中显示成功消息。 - 如果上传过程中发生异常,异常信息将显示在
labelUpload
标签中。 - 最后,确保
labelUpload
标签可见。
典型文件上传漏洞,未验证文件格式,导致攻击者可上传.aspx文件。
前端代码如下 /WebGoat/Content/UploadPathManipulation.aspx
:
代码审计关注点:
|
|
4.2. RCE 漏洞
/WebGoat/App_Code/Util.cs
如果 cmd 参数可控时,可执行系统命令
代码审计关注点:
|
|
4.3. SQL 注入漏洞
/WebGoat/Content/SQLInjection.aspx.cs
跟进 GetEmailByName 方法
采取参数拼接的方式,导致SQL注入。
代码审计关注点:
|
|
4.4. 任意文件读取/下载
/WebGoat/Content/PathManipulation.aspx.cs
33行filename可控,跟进 ResponseFile 函数:
未对文件路径进行过滤,直接读取文件
将读取的文件流写入到HTTP Response中,造成任意文件读:
代码审计关注点:
|
|
5. 查找程序入口点
在 .NET Web 项目中,入口点是程序开始执行的地方,确定了应用程序的初始化过程。入口点会因项目类型的不同而有所不同,以下是一些常见的 .NET Web 项目类型及其入口点的概述:
5.1. ASP.NET Core 项目
ASP.NET Core 项目的入口点通常在 Program.cs
文件中。该文件包含 Main
方法,这是应用程序的实际入口。
示例:
|
|
Main
** 方法**:应用程序从这里开始执行。CreateHostBuilder
方法用于创建和配置应用程序的主机。Startup
** 类**:指定在启动过程中要配置应用程序服务和中间件的类。
5.2. ASP.NET MVC 项目
在 ASP.NET MVC 项目中,入口点位于 Global.asax.cs
文件中,通常是 Application_Start
方法。
示例:
|
|
Application_Start
** 方法**:这是 MVC 应用程序的入口点,当应用程序启动时,ASP.NET 会调用这个方法,用于配置路由、过滤器、捆绑包等。
5.3. ASP.NET Web Forms 项目
ASP.NET Web Forms 项目的入口点也在 Global.asax.cs
文件中,同样使用 Application_Start
方法进行初始化。
示例:
|
|
6. 查找路由
在 .NET Web 项目中,路由定义了 URL 请求如何映射到特定的控制器和操作方法(或者处理程序)。当确定了程序入口点,以及存在风险的不安全函数后,如何访问到存在漏洞的页面呢?Web Forms 项目很简单,直接访问对应的 xxx.aspx
即可,其它架构下就需要寻找其路由定义。
6.1. ASP.NET Core 项目
ASP.NET Core 使用 Startup.cs
文件中的 Configure
方法来配置路由。通常你会看到类似如下的代码:
|
|
在这里,MapControllerRoute
方法定义了默认的路由模式,你可以在这个 UseEndpoints
方法中查看和定义项目的所有路由。
6.2. ASP.NET MVC 项目
在 ASP.NET MVC 项目中,路由通常配置在 RouteConfig.cs
文件中,该文件位于 App_Start
文件夹下:
|
|
7. 实战某ERP项目
这是一套典型的MVC架构,采取预编译形式,DLL 文件均放置在 bin 目录下:
首先拿到项目源码时,先关注如下几个文件:
Global.asax
:用于处理应用程序级别的事件,例如应用程序启动(Application_Start)、会话启动(Session_Start)、应用程序错误(Application_Error)等。
Web.config
:ASP.NET 应用程序的主要配置文件,用于定义应用程序的配置设置,例如数据库连接字符串、身份验证方式、自定义错误页面等。
其中 Global.asax
定义了程序入口点在 TraderXX.MvcApplication
类中
一般来说,DLL 文件名会对应上面 class的名称(或相近),在 bin 下找到对应的 DLL 文件:TraderXX.dll
,拖入ILSpy进行反编译:
寻找 MvcApplication
,在里面定义了一些基础路由:
然后寻找存在风险的函数,这里以文件上传为例,搜索 WriteAllBytes
关键字:
搜索到 EmailController
下定义了 UploadEmailAttr
action,其中
|
|
[ValidateInput(false)]:禁用请求输入验证。这意味着方法不会验证输入数据是否包含潜在的危险内容(例如 HTML 或脚本)。这通常在处理 HTML 或富文本编辑器数据时使用,但也可能会带来安全风险(如 XSS 攻击),因此需要谨慎使用。
[AcceptVerbs(HttpVerbs.Post)]:指定该方法只接受 HTTP POST 请求。这表明此方法仅在处理通过 POST 方法发送的请求时会被调用。
如下这种,则是需要身份认证后才可使用的 Action:
这一块有点类似于 Java 的注解,定义一些全局属性。
|
|
接下来的函数逻辑是从 HTTP Body 中读取内容,读取 HTTP name 参数作为文件后缀,虽然检测了文件后缀的内容,但是并未做拦截处理,最终利用System.IO.File.WriteAllBytes
直接写入到系统中,实现任意文件上传。
接下来就是寻找具体的路由定义了,由于在全局路由处并未定义具体的功能路由,因此搜索相关关键字,找到区域路由的定义:
|
|
这里定义了一个名为 JoinfAppAreaRegistration
的类,它继承自 AreaRegistration
类。AreaRegistration
是 ASP.NET MVC 中用于注册区域的基类。
|
|
该属性定义了区域的名称为 "JoinfApp"
。这个名称用于标识和访问该区域。
|
|
RegisterArea
方法是重写自 AreaRegistration
基类的一个抽象方法,用于注册该区域的路由。AreaRegistrationContext
参数提供了对当前区域路由的上下文信息。
|
|
这段代码为该区域定义了一个名为 "Index"
的路由。此路由将 URL 模式 "JoinfApp/Index"
映射到 Mobile
控制器的 Index
操作方法,并且将 AreaName
设置为 "JoinfApp"
。具体来说:
- URL 模式
"JoinfApp/Index"
将匹配类似http://xx/JoinfApp/Index
的请求。 - 该请求将被路由到
MobileController
的Index
方法。
|
|
此处定义了一个名为 "JoinfApp_default"
的默认路由,用于匹配区域内的大多数请求。它的 URL 模式是 "JoinfApp/{controller}/{action}/{id}"
,该路由可以匹配类似 http://xx/JoinfApp/ControllerName/ActionName/123
的请求。
{controller}
是控制器名称的占位符。{action}
是操作方法名称的占位符,默认值是"Index"
。{id}
是可选的参数,默认为空。
因此 payload 呼之欲出(公开漏洞,无风险):
|
|
8. 参考
https://wiki.owasp.org/index.php/Category:OWASP_WebGoat.NET