在针对一款基于 .NET Framework 开发的企业级 Web 应用进行安全评估时,代码审计是发现深层次安全漏洞的有效手段。本次审计实践将复盘三个典型漏洞的发现与验证过程,涵盖文件上传、SQL注入与身份认证绕过。
前期工作准备
开始审计前,首先需要获取并分析目标应用的源码。通常,我们可以通过反编译工具(如ILSpy)对发布的DLL程序集进行逆向,还原出可读的C#源码。反编译后,由于ILSpy的全局搜索功能有限,建议同时使用代码编辑器(如Visual Studio Code)打开项目文件夹,以便高效地搜索关键词和跟踪代码逻辑。
漏洞一:未校验路径的文件上传
在网站根目录下,通过文件名FileUpload.ashx很容易定位到一个文件上传功能点。通过反编译工具ILSpy查看其源码,发现FileUpload.ashx.cs文件定义了一个FileUpload类,它继承自UploadHandler。
反编译后的FileUpload.ashx关键代码如下:
using System;
using System.Linq;
using System.Web;
using JZSoft.Core.Tools.Upload;
using JZSoft.Service.Common;
using Newtonsoft.Json;
public class FileUpload : UploadHandler
{
public override string GetResult(string localFileName, string uploadFilePath)
{
if (err.Length > 0)
{
return JsonConvert.SerializeObject(new
{
msg = new
{
localName = localFileName
}
});
}
}
}
可以看到,该类主要负责上传成功后的结果(如缩略图路径)序列化为JSON返回。真正的上传逻辑在其父类UploadHandler中。继续跟进UploadHandler类,发现它创建了一个FileUploader实例来处理核心上传流程。
UploadHandler抽象类的关键逻辑如下:
using System.Web;
using JZSoft.Core.Tools.Upload;
public abstract class UploadHandler : IHttpHandler
{
protected FileUploader _fileUploader = new FileUploader();
protected virtual string[] ImageExt => _fileUploader.ImageExt;
protected UpfileResult Result { get { return set; } }
public bool IsReusable => false;
public abstract string GetResult(string localFileName, string uploadFilePath, string err);
public abstract void OnUploaded(HttpContext context, string filePath);
public void ProcessRequest(HttpContext context)
{
UpfileResult res = (Result = _fileUploader.Upload(context));
if (res.Success)
{
OnUploaded(context, res.FilePath);
context.Response.Write(GetResult(Result.LocalPath, Result.FilePath, Result.Error));
context.Response.End();
}
}
}
因此,审计的重点转向FileUploader类。在该类中,发现了对上传文件扩展名的白名单校验,允许上传的类型包括常见的文档、图片和压缩包,安全性看似有一定保障。
public virtual string[] AllowExt => new string[15]
{
"txt", "rar", "zip", "gif", "jpg", "jpeg", "bmp", "png", "swf", "xls", "xlsx", "doc", "docx", "pdf", "apk"
};
public virtual string[] ImageExt => new string[5] { "gif", "jpg", "jpeg", "bmp", "png" };
然而,在保存文件的SaveFile方法中,存在一个关键缺陷:上传的子目录(subfolder)参数直接取自用户请求,未做任何校验,这导致了目录遍历攻击的可能。
private string SaveFile(HttpContext context, string subFolder, string ext, string filePath, byte[] file)
{
string folder = (string.IsNullOrEmpty(context.Request["subfolder"])? "default" : context.Request["subfolder"]);
filePath = Path.Combine(UploadConfig.UploadPath, folder, subFolder);
// ... 后续文件保存逻辑
}
尽管有白名单限制,但由于上传路径可控,攻击者可以将文件上传到非预期目录,结合其他漏洞(如文件解析漏洞)可能扩大危害。通过构造一个包含subfolder参数的multipart/form-data请求,可以验证此上传点确实存在且未授权。
请求与响应示例:
- 请求报文: 向
/FileUpload.ashx发起POST请求,包含一个文件字段file(值为1.png)和一个可控的subfolder参数。
- 响应报文: 服务器返回状态码200,并包含一个JSON对象,其中
url字段显示了文件在服务器上的存储路径,路径中包含了用户可控的subfolder值,证实了路径可控的问题。
这本质上是一个未授权+路径可控的文件上传漏洞。在安全/渗透/逆向实践中,此类漏洞常被作为入口点。
漏洞二:拼接导致的SQL注入
为了寻找SQL注入点,使用代码编辑器的全局搜索功能查找select * from等常见SQL语句模式。结果中,TaskReportConfigController类引起了注意,其代码中存在大量明显的字符串拼接。
部分存在风险的代码逻辑如下:
if (!string.IsNullOrEmpty(YeWuHaoMa))
{
text2 = text2 + " and TBG_GamTaskInfoBM.TaskNumber Like '%" + YeWuHaoMa + "%'";
}
if (!string.IsNullOrEmpty(KuWeiName))
{
text2 = text2 + " and TB_StoreBM.StoreName Like'%" + KuWeiName + "%' ";
}
// ... 更多类似拼接
List<TB_TaskReportConfig> list = (from p in managerEFRepository.GetModel((TB_TaskReportConfig p) => p.IsDisable == 0)
orderby p.XuHao
select p).ToList();
for (int i = 0; i < list.Count; i++)
{
// ... 复杂的SQL语句动态构建过程
string text4 = "select text.Trim() from (select text2)"; // 注意,text2是用户输入拼接的字符串
DataTable datatable = new ManagerReportQuery(TB_TaskReportConfig).GetDataTable(text4, null);
ajaxResult.ResultTable = datatable.Rows.Count;
}
可以看到,用户传入的参数(如YeWuHaoMa、KuWeiName)被直接拼接到SQL查询条件字符串text2中,最终这个未经任何过滤的text2又被拼接到完整的SQL语句text4中,并交由GetDataTable方法执行,存在典型的SQL注入风险。
根据ASP.NET MVC的默认路由规则(/{Controller}/{Action}/{参数}),可推导出对应的访问路径。Controller为TaskReportConfig(去掉Controller后缀),Action为存在注入的方法名GetDataListZongHe。
然而,在构造请求测试时,访问被拒绝。回溯代码发现,TaskReportConfigController继承自BaseController,而BaseController类上标注了[CheckUserStateAuthorize]特性,这意味着所有继承该类的控制器方法都会先进行用户身份校验。
[CheckUserStateAuthorize]
public class BaseController : Controller
{
protected string errorPage = "Save";
private TBS_YongHu _currentUser;
protected TBS_YongHu CurrentUser
{
get
{
// ... 从当前用户身份信息中获取用户实体的逻辑
return _currentUser;
}
}
}
因此,要利用这个SQL注入漏洞,首先需要获得一个有效的登录身份。这也体现了在C#/.Net应用审计中,权限校验机制对漏洞利用条件的影响。
漏洞三:默认密码导致的弱口令与突破
面对需要登录才能触发的SQL注入点,我们转向身份认证系统。虽然网站存在用户名枚举和登录验证码延时等缺陷,但由于密码加密和错误次数限制,直接爆破收效甚微。
通过代码搜索“密码”、“重置”等关键词,在权限管理相关的配置或代码中,发现了用户忘记密码后的初始化密码值。
相关配置代码片段显示:
...
value="将重置初始化" icon="glyphicon glyphicon-trash" url="/sysManage/ext">
...
value="将重置初始化" icon="glyphicon glyphicon-pencil" url="/sysManage/ext">
这提示我们,系统存在一个默认的、固定的密码重置值。攻击者只需要遍历有效的用户名,并尝试使用这个默认的初始化密码,即可绕过复杂的加密和风控机制,实现“弱口令”攻击。
在通过此方法成功获取一个有效账户的登录权限后,我们便满足了BaseController的权限校验要求,之前发现的SQL注入漏洞得以成功复现和验证。
这个案例清晰地展示了一次完整的软件测试与安全审计路径:从文件上传功能切入,发现SQL注入风险,再通过代码审计找到认证系统的薄弱点(默认密码),最终将多个漏洞串联形成完整的攻击链。
参考资料
[1] .NET代码审计初识, 微信公众号:mp.weixin.qq.com/s/2fVLVUVrWm8TsoeEqeAjiQ
版权声明:本文由 云栈社区 整理发布,版权归原作者所有。