FEATURED · 精选文章

ASP.NET Core 模糊测试实战指南:基于 SharpFuzz 与 libFuzzer 构建与运行 Fuzzing Targets

发布时间 / 2026/9/10 21:34:42
来源 / 创域科博编辑部
栏目 / 资讯中心
ASP.NET Core 模糊测试实战指南:基于 SharpFuzz 与 libFuzzer 构建与运行 Fuzzing Targets ASP.NET Core 模糊测试实战指南基于 SharpFuzz 与 libFuzzer 构建与运行 Fuzzing Targets【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcoreASP.NET Core 仓库维护了一套独立的模糊测试Fuzzing基础设施它位于 src/Fuzzing 目录下一方面为各库提供以IFuzzer接口实现的模糊测试目标target另一方面提供从这些 target 生成 OneFuzz 云端部署包、以及本地运行与覆盖率采集的配套工具。本篇文章将完整讲解该体系的原理、本地运行步骤、崩溃复现与覆盖率分析流程并手把手演示如何新增一个 fuzzing target帮助你掌握为 ASP.NET Core 各类解析器、头信息处理与传输层代码构建自动化安全测试的完整链路。项目定位与技术选型与仓库里其他组件不同src/Fuzzing不是被引用的运行时库而是一套“对 ASP.NET Core 库做模糊测试”的工具项目。README 明确说明了它的两个职责提供针对各种 ASP.NET Core 库的fuzzing targets提供从这些 target 生成 OneFuzz 部署deployment的支撑代码。技术栈上有两条主线从源码中可以逐一对上SharpFuzz负责对被测程序集进行插桩instrumentation。仓库在 AspNetCoreFuzzing.csproj 中直接引用SharpFuzz包并在部署准备阶段调用名为sharpfuzz的 CLI 工具对目标程序集做二进制改写详见 Program.cs 的InstrumentAssemblies方法。libFuzzer负责运行时驱动它不断生成随机/变异的输入字节流喂给插桩后的代码并利用插桩反馈进行覆盖引导coverage-guided的进化。运行时通过微软维护的libfuzzer-dotnet.exe启动宿主进程再由宿主把输入交给 .NET target。README 指出该工程是仿照 dotnet/runtime 仓库中运行时模糊测试基础设施搭建的。仓库中持续模糊测试continuous fuzzing运行在 Windows 环境上因此 README 中所有本地命令均以 Windows.cmd/.ps1/.bat脚本给出文章后续命令默认同样适用于 Windows 环境。源码结构与部署产物src/Fuzzing下的文件布局非常精简src/Fuzzing/ ├── README.md # 使用说明本文主题文档 └── AspNetCoreFuzzing/ # 自包含的 fuzzing 宿主工程 ├── AspNetCoreFuzzing.csproj # 自包含发布、引用 SharpFuzz 与目标库 ├── IFuzzer.cs # fuzzing target 抽象接口 ├── Program.cs # 宿主入口反射发现、插桩、OneFuzz 部署 ├── Assert.cs # 供 fuzzer 内部使用的轻量断言 ├── run.bat # 一键调用 prepare-onefuzz 生成 deployment ├── collect-coverage.ps1 # 用 fuzz 语料采集被测库的代码覆盖率 └── Fuzzers/ └── MultipartReaderFuzzer.cs # 现有 target 的参照实现关键点是AspNetCoreFuzzing.csproj中SelfContainedtrue、默认RuntimeIdentifier为win-x64源码。这意味着构建后除了AspNetCoreFuzzing.exe宿主还会复制一份运行所需的所有依赖库到输出目录——这正是插桩和后续 OneFuzz 打包的前提插桩工具需要就地改写目标 DLL而云端机器上不再需要额外还原依赖。本地运行前的准备工作按 README两步即可准备好环境。第一步激活仓库内的 .NET 本地环境。在仓库根目录执行.\activate.ps1仓库采用“本地 dotnet”机制脚本会定位根目录下.dotnet目录覆盖率脚本中也能看到它的使用见 collect-coverage.ps1因此后续命令都应在这个已激活的 shell 中执行。第二步安装 SharpFuzz 命令行工具用于对程序集做插桩dotnet tool install --global SharpFuzz.CommandLine安装后sharpfuzz path-to-dll命令即可对单个程序集执行插桩宿主程序在部署阶段会以子进程方式调用它Program.cs。注意宿主工程 AspNetCoreFuzzing.csproj 目前引用了Microsoft.Net.Http.Headers、Microsoft.AspNetCore.WebUtilities、Microsoft.AspNetCore.Http.*、Microsoft.AspNetCore.Components.Endpoints、Microsoft.AspNetCore.Server.Kestrel.Core等目标库新增 target 时若涉及新库需要在此补充引用。构建并启动一次模糊测试构建宿主工程进入工程目录并构建cd src/Fuzzing/AspNetCoreFuzzing dotnet build由于是自包含发布构建产物中会同时出现AspNetCoreFuzzing.exe与全部依赖库副本。生成 deployment 目录run.bat会在artifacts\bin\AspNetCoreFuzzing下递归搜索宿主 exe并以prepare-onefuzz deployment参数调用它run.bat。该命令会为每个 fuzzing target创建一个独立目录完成以下工作对应 Program.cs 的PrepareFuzzer将发布目录中所有文件复制到该 target 的目录按IFuzzer.Dictionary/IFuzzer.Corpus声明复制字典与种子语料用 SharpFuzz 对TargetAssemblies指定的程序集插桩写入OneFuzzConfig.json云端配置与local-run.bat本地运行脚本。在反复迭代代码时需要每次先重新构建再运行dotnet build run.bat细节插桩并非盲目重复执行。InstrumentAssemblies会把当前 DLL 与上次记录的.original副本做字节比较仅当程序集发生变化时才重新调用 sharpfuzz避免无谓重插桩Program.cs。在 Windows 上它还会设置SHARPFUZZ_INSTRUMENT_MIXED_MODE_ASSEMBLIES1环境变量以支持 mixed-mode 程序集插桩。启动本地模糊测试prepare-onefuzz在每个 target 目录里生成的local-run.bat本质是一条libfuzzer-dotnet.exe --target_pathAspNetCoreFuzzing.exe --target_argFuzzerName启动命令Program.cs并会把附加参数原样透传给 libFuzzer。以现有MultipartReaderFuzzer为例deployment\MultipartReaderFuzzer\local-run.bat默认情况下 libFuzzer 会无限期运行不断生成并测试随机输入按CtrlC即可停止。当它发现有趣输入或崩溃时会把文件写入当前目录crash-hash—— 导致 fuzzer 崩溃的输入这代表一个真实的 Bugtimeout-hash—— 导致单次执行超过超时上限而挂起的输入这可能是 Bug如死循环、极端慢路径。常用 libFuzzer 选项libFuzzer 暴露了大量运行时选项README 摘录了最有用的几个Option说明-max_total_time600600 秒10 分钟后停止-timeout30将任何单次输入执行 30 秒视为超时-jobs5并行运行 5 个 fuzzer 实例-max_len1024限制生成输入的最大字节数为 1024例如对multipart-inputs语料目录跑 10 分钟、并行多实例deployment\MultipartReaderFuzzer\local-run.bat multipart-inputs -timeout30 -max_total_time600 -jobs5注意这里multipart-inputs是用户提供的语料目录libFuzzer 会把新发现的输入回写到语料中用于不断回归与覆盖演化。崩溃与超时的调查复现libFuzzer 落盘的crash-*/timeout-*文件都是二进制输入样本。宿主程序支持“指定 fuzzer 输入文件或目录”两种模式做确定性复现cd src/Fuzzing/AspNetCoreFuzzing :: 复现单个崩溃文件 dotnet run -- MultipartReaderFuzzer C:\path\to\crash-abc123 :: 复现某目录下全部崩溃/超时文件 dotnet run -- MultipartReaderFuzzer C:\path\to\directory-with-crash-files\宿主收到文件/目录参数后会跳过 libFuzzer 循环直接逐个读取输入并调用fuzzer.FuzzTarget(...)Program.cs非常适合做回归验证。由于工程自包含也可不经dotnet run直接运行 exeartifacts\bin\AspNetCoreFuzzing\Debug\net11.0\win-x64\AspNetCoreFuzzing.exe MultipartReaderFuzzer crash-abc123产物路径中的net11.0/win-x64随仓库当前目标框架与运行时标识而定。需要交互式调试时在对应 fuzzer 的FuzzTarget方法内打断点再用调试器启动即可dotnet run --no-build -- MultipartReaderFuzzer crash-abc123如何列出所有可用的 fuzzerstarget 是通过反射自动发现的见后文因此直接以无参数方式运行宿主即可打印全部可用列表dotnet run --no-build用真实语料生成覆盖率报告模糊测试的价值不止于找崩溃还在于评估代码路径的覆盖程度。仓库提供了 collect-coverage.ps1思路是先用 fuzzer 跑一段时间产出语料再以这些输入驱动目标代码并采集被测程序集的覆盖率。mkdir multipart-inputs deployment\MultipartReaderFuzzer\local-run.bat multipart-inputs .\collect-coverage.ps1 MultipartReaderFuzzer multipart-inputs脚本内部会依次完成检查/安装coverlet.console与dotnet-reportgenerator-globaltool→ 构建工程 → 通过dotnet run -- Fuzzer --get-instrumented-assemblies读取需要统计的程序集 → 用 coverlet 以这些输入为测试数据采集覆盖率OpenCover 格式→ 用 ReportGenerator 生成 HTML。报告位于.\coverage-report\html\index.html其中--get-instrumented-assemblies是宿主内置的隐藏参数会把某 fuzzer 声明的TargetAssemblies自动补.dll后缀逐行输出Program.cs 与 Program.cs。创建新的 Fuzzing TargetIFuzzer 接口契约新增一个 target 只需实现 IFuzzer.cs 定义的内部接口internal interface IFuzzer { // 友好名称默认取类名用于命令行与目录命名 string Name GetType().Name; // 需要被插桩的程序集列表即被测代码所在程序集 string[] TargetAssemblies { get; } // 可选用于引导 fuzzer 的字典文件名 string? Dictionary null; // 可选作为初始语料的目录名 string? Corpus null; // 每个测试输入都会调用的入口需尽量覆盖目标程序集中的代码路径 void FuzzTarget(ReadOnlySpanbyte bytes); }一个最小示例MediaType 解析永不抛异常README 给出的经典例子是验证MediaTypeHeaderValue.TryParse在任何畸形输入下都不抛异常internal sealed class MediaTypeFuzzer : IFuzzer { public string[] TargetAssemblies [Microsoft.Net.Http.Headers]; public void FuzzTarget(ReadOnlySpanbyte bytes) { string input Encoding.UTF8.GetString(bytes); _ MediaTypeHeaderValue.TryParse(input, out _); } }两个核心成员的含义TargetAssemblies被测代码所在、需要被 SharpFuzz 插桩的程序集。宿主在部署阶段会就地改写这些 DLL为其注入覆盖率探针若某 target 不声明任何目标程序集部署时会直接报错“Specify at least one target”。FuzzTargetfuzzer 为每个测试输入执行的逻辑必须真正触达目标程序集中的代码路径否则插桩与覆盖引导都失去意义。方法签名接收ReadOnlySpanbyte保证 libFuzzer 的字节流可以零拷贝进入 target。反射发现机制所有IFuzzer实现都是自动注册的宿主在启动时扫描自身程序集筛选出所有非抽象、实现了IFuzzer的类并实例化再按名称排序Program.cs。这意味着新 target 无需改任何注册表/清单代码加类即可被本地运行与 CI 持续模糊测试识别宿主会为每个 target 自动生成deployment\FuzzerName\local-run.bat与OneFuzzConfig.json。提交新 fuzzer 时的额外注意点CI 流水线 .azure/pipelines/fuzzing/deploy-to-onefuzz.yml 中的条目是自动生成的每次执行run.bat即prepare-onefuzz时宿主都会重写 YAML 中# ONEFUZZ_TASK_WORKAROUND_START与# ONEFUZZ_TASK_WORKAROUND_END标记之间的内容为每个 fuzzer 生成独立的onefuzz-task0步骤Program.cs。因此提交新 fuzzer 时必须连同更新后的 YAML 一起提交。说明这个“逐个生成任务”的做法源自一个已知限制——OneFuzz 尚不能在同一部署中处理多个共享相似程序集/PBD 的任务仓库通过为每个 fuzzer 独立成步来规避。YAML 采用 CRLF 行尾由宿主统一处理。真实案例纵深MultipartReaderFuzzer仓库当前唯一的正式 target MultipartReaderFuzzer.cs 是理解编写思路的最佳范本。它模糊测试的是MultipartReader——负责按 RFC 2046 从 HTTP 请求体解析 multipart 表单数据的解析器文件上传功能直接暴露给不可信网络数据安全风险面大是典型的模糊测试对象。其FuzzTarget实现值得拆解public string[] TargetAssemblies [Microsoft.AspNetCore.WebUtilities]; public void FuzzTarget(ReadOnlySpanbyte bytes) { if (bytes.Length 4) return; // 用前两个字节推导 boundary 长度1-32 字符 int boundaryLength (bytes[0] % 32) 1; ... // 从输入中切出 boundary 串并清洗空字符与空白 var boundary Encoding.ASCII.GetString(bytes.Slice(headerOffset, boundaryLength)); boundary boundary.Replace(\0, x); if (string.IsNullOrWhiteSpace(boundary)) boundary boundary; var body bytes[(headerOffset boundaryLength)..]; TestMultipartReader(body.ToArray(), boundary, async: false).GetAwaiter().GetResult(); TestMultipartReader(body.ToArray(), boundary, async: true).GetAwaiter().GetResult(); }设计上它把“随机字节”结构化为“boundary 头 multipart body”既维持了输入的高自由度又保证足够比例的输入能穿过前置解析逻辑从而高效触达深层代码。其TestMultipartReader设置了贴近真实使用习惯的限额HeadersCountLimit 32、HeadersLengthLimit 8KB、BodyLengthLimit 64KB逐段读取所有 section 并同步、异步两条路径都跑一遍最大化覆盖读取逻辑同时显式吞掉InvalidDataException/IOException/InvalidOperationException/ArgumentException这类畸形输入下的“预期异常”让非预期异常才能浮出表面成为崩溃信号。这与 README 中MediaTypeFuzzer的“TryParse 永不抛”命题是同一思路的两种写法fuzzer 的价值正是把“库应该优雅处理畸形输入”这类不变量变成机器可执行的自动断言。用真实输入做回归验证宿主程序的第二个参数支持单文件或目录这让 fuzzing 产物可以直接变成回归测试集cd src/Fuzzing/AspNetCoreFuzzing :: 对整个目录中的样本逐个执行 dotnet run -- MultipartReaderFuzzer inputs :: 对单个样本文件执行 dotnet run -- MultipartReaderFuzzer inputs/sample.bin实操建议与 README 的表述一致把有价值的输入或历史崩溃文件保存下来在修复之后重新跑一遍确认问题真正闭环。若传入的是目录宿主会枚举其中所有文件逐个执行若是文件则只执行该文件Program.cs。补充host 进程对输入的硬性约束与 OneFuzz 配置8 字节对齐约束在真正的 libFuzzer 循环中宿主会在把字节交给 target 前校验输入内存地址的 8 字节对齐不满足时直接抛出ArgumentOutOfRangeExceptionProgram.cs。注释说明了原因部分 fuzzer 假定输入至少 2 字节对齐。因此新编写 target 时无需自行对齐但应意识到该约束由宿主统一保障。字典与语料的配套校验若 target 声明了Dictionary或Corpus宿主会要求发布目录的Dictionaries/、Corpora/中存在对应文件/目录否则部署直接失败Program.cs。字典会以-dict参数传给 libFuzzer语料则作为初始种子目录在本地脚本中种子语料会放在用户追加参数之后拼接从而保证用户另行指定的语料目录能优先接收新输入Program.cs。Dictionaries文件夹在工程文件中通过None IncludeDictionaries\*随构建复制AspNetCoreFuzzing.csproj。云端部署的下载校验prepare-onefuzz首次运行时需要下载libfuzzer-dotnet.exe宿主会对下载文件做SHA-512 哈希比对不匹配则拒绝使用Program.cs避免供应链上的中间人替换风险。生成的OneFuzzConfig.json中还包含云端行为编排每次任务超时 120 秒、单输入超时 60 秒、崩溃会自动创建 ADO 工作项并配置去重字段与状态流转规则Program.cs非 CI 环境未设置TF_BUILD提交的任务名会追加-local后缀以便区分。小结ASP.NET Core 的 fuzzing 体系遵循一条清晰的流水线IFuzzer定义被测逻辑 → 反射自动发现 → SharpFuzz 对目标程序集插桩 → libFuzzer/libfuzzer-dotnet 覆盖引导执行 → 崩溃/超时样本落盘 → 复现与覆盖率回归。在此基础上新增一个 target 的成本被压缩到“写一个类”的粒度而本地调试、覆盖率分析与 OneFuzz 云端任务生成全部由 Program.cs 自动完成。无论是想为某段解析逻辑建立持续的安全保障还是希望沉淀一套可复用的“畸形输入即回归用例”的工作流都可以以 MultipartReaderFuzzer.cs 为模板快速起步。【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻