news 2026/9/10 21:34:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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/aspnetcore

ASP.NET Core 仓库维护了一套独立的模糊测试(Fuzzing)基础设施,它位于 src/Fuzzing 目录下:一方面为各库提供以IFuzzer接口实现的模糊测试目标(target),另一方面提供从这些 target 生成 OneFuzz 云端部署包、以及本地运行与覆盖率采集的配套工具。本篇文章将完整讲解该体系的原理、本地运行步骤、崩溃复现与覆盖率分析流程,并手把手演示如何新增一个 fuzzing target,帮助你掌握为 ASP.NET Core 各类解析器、头信息处理与传输层代码构建自动化安全测试的完整链路。

项目定位与技术选型

与仓库里其他组件不同,src/Fuzzing不是被引用的运行时库,而是一套“对 ASP.NET Core 库做模糊测试”的工具项目。README 明确说明了它的两个职责:

  1. 提供针对各种 ASP.NET Core 库的fuzzing targets
  2. 提供从这些 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.csprojSelfContained=true、默认RuntimeIdentifierwin-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.HeadersMicrosoft.AspNetCore.WebUtilitiesMicrosoft.AspNetCore.Http.*Microsoft.AspNetCore.Components.EndpointsMicrosoft.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_ASSEMBLIES=1环境变量,以支持 mixed-mode 程序集插桩。

启动本地模糊测试

prepare-onefuzz在每个 target 目录里生成的local-run.bat本质是一条libfuzzer-dotnet.exe --target_path=AspNetCoreFuzzing.exe --target_arg=<FuzzerName>启动命令(Program.cs),并会把附加参数原样透传给 libFuzzer。以现有MultipartReaderFuzzer为例:

deployment\MultipartReaderFuzzer\local-run.bat

默认情况下 libFuzzer 会无限期运行,不断生成并测试随机输入;按Ctrl+C即可停止。当它发现有趣输入或崩溃时,会把文件写入当前目录:

  • crash-<hash>—— 导致 fuzzer 崩溃的输入,这代表一个真实的 Bug
  • timeout-<hash>—— 导致单次执行超过超时上限而挂起的输入,这可能是 Bug(如死循环、极端慢路径)

常用 libFuzzer 选项

libFuzzer 暴露了大量运行时选项,README 摘录了最有用的几个:

Option说明
-max_total_time=600600 秒(10 分钟)后停止
-timeout=30将任何单次输入执行 >30 秒视为超时
-jobs=5并行运行 5 个 fuzzer 实例
-max_len=1024限制生成输入的最大字节数为 1024

例如:对multipart-inputs语料目录跑 10 分钟、并行多实例:

deployment\MultipartReaderFuzzer\local-run.bat multipart-inputs -timeout=30 -max_total_time=600 -jobs=5

注意这里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直接运行 exe:

artifacts\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

如何列出所有可用的 fuzzers

target 是通过反射自动发现的(见后文),因此直接以无参数方式运行宿主即可打印全部可用列表:

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.consoledotnet-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 Target

IFuzzer 接口契约

新增一个 target 只需实现 IFuzzer.cs 定义的内部接口:

internal interface IFuzzer { // 友好名称,默认取类名,用于命令行与目录命名 string Name => GetType().Name; // 需要被插桩的程序集列表,即被测代码所在程序集 string[] TargetAssemblies { get; } // 可选:用于引导 fuzzer 的字典文件名 string? Dictionary => null; // 可选:作为初始语料的目录名 string? Corpus => null; // 每个测试输入都会调用的入口,需尽量覆盖目标程序集中的代码路径 void FuzzTarget(ReadOnlySpan<byte> bytes); }

一个最小示例:MediaType 解析永不抛异常

README 给出的经典例子是验证MediaTypeHeaderValue.TryParse在任何畸形输入下都不抛异常:

internal sealed class MediaTypeFuzzer : IFuzzer { public string[] TargetAssemblies => ["Microsoft.Net.Http.Headers"]; public void FuzzTarget(ReadOnlySpan<byte> bytes) { string input = Encoding.UTF8.GetString(bytes); _ = MediaTypeHeaderValue.TryParse(input, out _); } }

两个核心成员的含义:

  • TargetAssemblies:被测代码所在、需要被 SharpFuzz 插桩的程序集。宿主在部署阶段会就地改写这些 DLL,为其注入覆盖率探针;若某 target 不声明任何目标程序集,部署时会直接报错“Specify at least one target”。
  • FuzzTarget:fuzzer 为每个测试输入执行的逻辑,必须真正触达目标程序集中的代码路径,否则插桩与覆盖引导都失去意义。方法签名接收ReadOnlySpan<byte>,保证 libFuzzer 的字节流可以零拷贝进入 target。

反射发现机制

所有IFuzzer实现都是自动注册的:宿主在启动时扫描自身程序集,筛选出所有非抽象、实现了IFuzzer的类并实例化,再按名称排序(Program.cs)。这意味着:

  • 新 target 无需改任何注册表/清单代码,加类即可被本地运行与 CI 持续模糊测试识别;
  • 宿主会为每个 target 自动生成deployment\<FuzzerName>\local-run.batOneFuzzConfig.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-task@0步骤(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(ReadOnlySpan<byte> 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 = 32HeadersLengthLimit = 8KBBodyLengthLimit = 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 字节对齐,不满足时直接抛出ArgumentOutOfRangeException(Program.cs)。注释说明了原因:部分 fuzzer 假定输入至少 2 字节对齐。因此新编写 target 时无需自行对齐,但应意识到该约束由宿主统一保障。

字典与语料的配套校验

若 target 声明了DictionaryCorpus,宿主会要求发布目录的Dictionaries/Corpora/中存在对应文件/目录,否则部署直接失败(Program.cs)。字典会以-dict=参数传给 libFuzzer,语料则作为初始种子目录;在本地脚本中,种子语料会放在用户追加参数之后拼接,从而保证用户另行指定的语料目录能优先接收新输入(Program.cs)。Dictionaries文件夹在工程文件中通过None Include="Dictionaries\*"随构建复制(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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 21:34:33

MCGS 7.7仿真在鼓风机气压检测系统中的应用实践

白天工厂里鼓风机房热浪加噪声&#xff0c;晚上想改程序又怕现场设备配合不了。做鼓风机气压检测系统这些年&#xff0c;我最大的体会是——真正费时间的从来不是接线和组态&#xff0c;而是没完没了的反复调试。所以我现在养成一个习惯&#xff1a;所有鼓风机气压检测的画面、…

作者头像 李华
网站建设 2026/9/10 21:34:08

CANN/ge离线图编译执行示例

Sample Usage Guide 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、Tensor…

作者头像 李华
网站建设 2026/9/10 21:34:04

Java大数据技术在智能医疗中的应用与实践

1. 智能医疗时代的数据革命电子病历数据正以每年40%的速度增长&#xff0c;三甲医院平均每天产生超过5TB的临床记录。这些数据如果只是静态存储&#xff0c;就如同埋在地下的石油&#xff0c;无法发挥其真正的价值。我在参与某省级医疗大数据平台建设时&#xff0c;亲眼见证过这…

作者头像 李华
网站建设 2026/9/10 21:32:17

AI网关日志裸奔事件复盘:构建可落地的AI安全防线

2026年3月的一个凌晨&#xff0c;我被一通告警电话从床上拽了起来。电话那头&#xff0c;值班同学的嗓子是哑的&#xff1a;“出事了&#xff0c;日志接口在外面裸奔。” 那段时间我正好带着团队做 AI 网关的安全加固&#xff0c;听到这句话&#xff0c;脑子里的第一反应不是“…

作者头像 李华
网站建设 2026/9/10 21:22:21

豆包学习法:构建高效知识管理系统的技术实践

1. 为什么豆包能成为学习神器&#xff1f; 作为一个每天要处理大量信息的知识工作者&#xff0c;我试过市面上几乎所有主流学习工具&#xff0c;直到去年偶然发现豆包这个不起眼的小工具&#xff0c;彻底改变了我的学习效率。你可能在社交平台刷到过"豆包学习法"的分…

作者头像 李华