Go语言 HTTP 服务模糊测试教程

写在前面: 此博客内容已经同步到我的博客网站,如需要获得更优的阅读体验请前往https://blog.mainjay.cloudns.ch/blog/go/fuzzing-test

作为开发人员,我们并不总能预见到程序或函数可能接收到的所有可能输入。

即使我们可以定义主要的边界情况,但仍然无法预测程序在面对一些奇怪的意外输入时会如何表现。换句话说,我们通常只能发现预期会出现的bug。

这就是模糊测试(Fuzzing)派上用场的地方。在本教程中,你将学习如何在 Go 中进行模糊测试。

什么是模糊测试?

模糊测试是一种自动化的软件测试技术,它涉及向计算机程序输入大量有效的、接近有效的或无效的随机数据,并观察其行为和输出。模糊测试的目标是揭示通过传统测试方法可能无法发现的bug、崩溃和安全漏洞。

这段Go代码在正常情况下运行良好,除非你提供某些特定输入:

func Equal(a []byte, b []byte) bool {for i := range a {// 可能会因运行时错误而崩溃:索引超出范围if a[i] != b[i] {return false}}return true
}

这个示例函数在两个切片长度相等时可以完美工作。但当第一个切片比第二个长时,它会发生崩溃(索引超出范围错误)。此外,当第二个切片是第一个切片的子集时,它也不会返回正确的结果。

模糊测试技术通过用各种输入轰炸这个函数,可以轻松发现这个bug。

将模糊测试集成到团队的软件开发生命周期(SDLC)中也是一个很好的实践。例如,微软在其SDLC中使用模糊测试作为阶段之一,以发现潜在的bug和漏洞。

Go中的模糊测试

虽然已经有许多模糊测试工具存在很长时间了(例如 oss-fuzz),但自 Go 1.18 开始,模糊测试被添加到了Go的标准库中。现在它作为常规测试包的一部分,因为它是测试的一种。你还可以将它与其他测试原语一起使用,这很方便。

在Go中创建模糊测试的步骤如下:

  1. _test.go 文件中,创建一个以 Fuzz 开头并接受 *testing.F 的函数
  2. 使用 f.Add() 添加语料库种子,让模糊测试器基于它生成数据
  3. 使用 f.Fuzz() 调用模糊测试目标,传递我们的目标函数接受的模糊测试参数
  4. 使用常规的 go test 命令启动模糊测试器,但要加上 --fuzz=Fuzz 标志

注意,模糊测试参数只能是以下类型:

- string, byte, []byte
- int, int8, int16, int32/rune, int64
- uint, uint8, uint16, uint32, uint64
- float32, float64
- bool

上面Equal函数的简单模糊测试可能如下所示:

// 模糊测试
func FuzzEqual(f *testing.F) {// 添加种子语料库f.Add([]byte{'f', 'u', 'z', 'z'}, []byte{'t', 'e', 's', 't'})// 带有模糊测试参数的模糊测试目标f.Fuzz(func(t *testing.T, a []byte, b []byte) {// 调用我们的目标函数并传递模糊测试参数Equal(a, b)})
}

默认情况下,模糊测试会永远运行,所以你要么需要指定时间限制,要么等待模糊测试失败。你可以使用 --fuzz 参数指定要运行的测试。

go test --fuzz=Fuzz -fuzztime=10s

如果执行过程中出现任何错误,输出应该类似于这样:

go test --fuzz=Fuzz -fuzztime=30s
--- FAIL: FuzzEqual (0.02s)--- FAIL: FuzzEqual (0.00s)testing.go:1591: panic: runtime error: index out of rangeFailing input written to testdata/fuzz/FuzzEqual/84ed65595ad05a58To re-run:go test -run=FuzzEqual/84ed65595ad05a58

注意,导致模糊测试失败的输入被写入 testdata 文件夹中的文件,可以使用该输入标识符重新运行:

go test -run=FuzzEqual/84ed65595ad05a58

testdata 文件夹可以提交到代码库中,并用于常规测试,因为模糊测试在没有 --fuzz 标志的情况下也可以作为常规测试运行。

HTTP服务的模糊测试

通过为你的 HandlerFunc 编写测试并使用 httptest 包,也可以对 HTTP 服务进行模糊测试。如果你需要测试整个 HTTP 服务而不仅仅是底层函数,这会非常有用。

让我们现在介绍一个更真实的示例,比如一个在请求体中接受用户输入的 HTTP Handler,然后为它编写模糊测试。

我们的处理程序接受一个带有 limitoffset 字段的 JSON 请求,用于对一些静态模拟数据进行分页。让我们先定义类型:

type Request struct {Limit  int `json:"limit"`Offset int `json:"offset"`
}type Response struct {Results    []int `json:"items"`PagesCount int   `json:"pagesCount"`
}

然后我们的处理函数解析 JSON,对静态切片进行分页,并在响应中返回新的 JSON。

func ProcessRequest(w http.ResponseWriter, r *http.Request) {var req Request// 解码 JSON 请求if err := json.NewDecoder(r.Body).Decode(&req); err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}// 对一些静态数据应用 offset 和 limitall := make([]int, 1000)start := req.Offsetend := req.Offset + req.Limitres := Response{Results:    all[start:end],PagesCount: len(all) / req.Limit,}// 发送 JSON 响应if err := json.NewEncoder(w).Encode(res); err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}w.WriteHeader(http.StatusOK)
}

你可能已经注意到,这个函数处理切片操作不太好,很容易发生崩溃。另外,如果试图除以0,它也会崩溃。如果我们能在开发期间或仅使用单元测试就发现这一点那很好,但有时并不是所有东西都能被我们看到,而且我们的处理程序可能会将输入传递给其他函数等等。

按照上面的 FuzzEqual 示例,让我们为 ProcessRequest 处理程序实现一个模糊测试。首先我们需要为模糊测试器提供示例输入。这是模糊测试器将用来修改并尝试的数据。我们可以制作一些示例 JSON 请求,并使用 f.Add()[]byte 类型。

func FuzzProcessRequest(f *testing.F) {// 为模糊测试器创建示例输入testRequests := []Request{{Limit: -10, Offset: -10},{Limit: 0, Offset: 0},{Limit: 100, Offset: 100},{Limit: 200, Offset: 200},}// 添加到种子语料库for _, r := range testRequests {if data, err := json.Marshal(r); err == nil {f.Add(data)}}// ...
}

之后我们可以使用 httptest 包创建一个测试 HTTP 服务器并向其发出请求。

注意:由于我们的模糊测试器可能生成无效的非 JSON 请求,最好直接跳过它们并使用 t.Skip() 忽略。我们也可以跳过 BadRequest 错误。

func FuzzProcessRequest(f *testing.F) {// ...// 创建测试服务器srv := httptest.NewServer(http.HandlerFunc(ProcessRequest))defer srv.Close()// 带有单个 []byte 参数的模糊测试目标f.Fuzz(func(t *testing.T, data []byte) {var req Requestif err := json.Unmarshal(data, &req); err != nil {// 跳过模糊测试期间可能生成的无效 JSON 请求t.Skip("invalid json")}// 将数据传递给服务器resp, err := http.DefaultClient.Post(srv.URL, "application/json", bytes.NewBuffer(data))if err != nil {t.Fatalf("unable to call server: %v, data: %s", err, string(data))}defer resp.Body.Close()// 跳过 BadRequest 错误if resp.StatusCode == http.StatusBadRequest {t.Skip("invalid json")}// 检查状态码if resp.StatusCode != http.StatusOK {t.Fatalf("non-200 status code %d", resp.StatusCode)}})
}

我们的模糊测试目标有一个类型为 []byte 的单个参数,其中包含完整的 JSON 请求,但你可以更改它以具有多个参数。

现在一切都准备好了,可以运行我们的模糊测试了。在对 HTTP 服务器进行模糊测试时,你可能需要调整并行工作器的数量,否则负载可能会使测试服务器不堪重负。你可以通过设置 -parallel=1 标志来做到这一点。

go test --fuzz=Fuzz -fuzztime=10s -parallel=1
go test --fuzz=Fuzz -fuzztime=30s
--- FAIL: FuzzProcessRequest (0.02s)--- FAIL: FuzzProcessRequest (0.00s)runtime error: integer divide by zeroruntime error: slice bounds out of range

正如预期的那样,我们会看到上述错误被发现。

我们还可以在 testdata 文件夹中看到模糊测试输入,看看是哪个 JSON 导致了这个失败。这是文件的示例内容:

go test fuzz v1
[]byte("{"limit":0,"offset":0}")

要修复这个问题,我们可以引入输入验证和默认设置:

if req.Limit <= 0 {req.Limit = 1
}
if req.Offset < 0 {req.Offset = 0
}
if req.Offset > len(all) {start = len(all) - 1
}
if end > len(all) {end = len(all)
}

有了这个改变,模糊测试将运行10秒并在没有错误的情况下退出。

结论

为你的 HTTP 服务或任何其他方法编写模糊测试是发现难以发现的 bug 的好方法。模糊测试器可以检测到只在某些奇怪的意外输入时才会发生的难以发现的 bug

看到模糊测试成为 Go 内置测试库的一部分是很棒的,这使得它很容易与常规测试结合使用。注意:在 Go 1.18 之前,开发人员使用 go-fuzz,这也是一个很好的模糊测试工具。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/60356.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记——PLCT汪辰:开发RISC-V上的操作系统(持续更新)

目录 第0章 下载源码 运行环境 构建和使用说明 第1章 记录一个本人没听说过的架构 第2章~第4章 第0章 下载源码 git clone https://gitee.com/unicornx/riscv-operating-system-mooc.git 运行环境 推荐使用 Ubuntu 20.04&#xff0c;Ubuntu 20.04 是目前最新的 Ubun…

Redis集群模式之Redis Sentinel vs. Redis Cluster

在分布式系统环境中&#xff0c;Redis以其高性能、低延迟和丰富的数据结构而广受青睐。随着数据量的增长和访问需求的增加&#xff0c;单一Redis实例往往难以满足高可用性和扩展性的要求。为此&#xff0c;Redis提供了两种主要的集群模式&#xff1a;Redis Sentinel和Redis Clu…

python opencv3

三、图像预处理2 1、图像滤波 为图像滤波通过滤波器得到另一个图像。也就是加深图像之间的间隙&#xff0c;增强视觉效果&#xff1b;也可以模糊化间隙&#xff0c;造成图像的噪点被抹平。 2、卷积核 在深度学习中&#xff0c;卷积核越大&#xff0c;看到的信息越多&#xff0…

JAVA后端生成图片滑块验证码 springboot+js完整案例

前言 现在大部分网部都是图片滑块验证码&#xff0c;这个得要与后端联动起来才是确保接口安全性 通过我们系统在发送手机短息时都会选进行滑块验证&#xff0c;但是我们要保证发送短息接口的全安&#xff0c;具体路思如下 那么这个滑块的必须是与后端交互才能保证安全性&…

【因果分析方法】MATLAB计算Liang-Kleeman信息流

【因果分析方法】MATLAB计算Liang-Kleeman信息流 1 Liang-Kleeman信息流2 MATLAB代码2.1 函数代码2.2 案例参考Liang-Kleeman 信息流(Liang-Kleeman Information Flow)是由 Liang 和 Kleeman 提出的基于信息论的因果分析方法。该方法用于量化变量之间的因果关系,通过计算信息…

6. ARM_ARM指令寻址

概念 什么是寻址方式&#xff1a; 寻址方式就是CPU去寻找一个操作数的方式。 ARM指令寻址的种类&#xff1a; ARM指令寻址有立即寻址、寄存器寻址、寄存器移位寻址、寄存器间接寻址、基址加变址寻址、等。 种类 1、立即寻址 立即寻址就是操作数直接包含在指令中&#…

在 Oracle Linux 8.9 上安装Oracle Database 23ai 23.5

在 Oracle Linux 8.9 上安装Oracle Database 23ai 23.5 1. 安装 Oracle Database 23ai2. 连接 Oracle Database 23c3. 重启启动后&#xff0c;手动启动数据库4. 重启启动后&#xff0c;手动启动 Listener5. 手动启动 Pluggable Database6. 自动启动 Pluggable Database7. 设置开…

【debug记录】MATLAB内置reshape与Python NumPy库reshape的差异

【debug记录】MATLAB内置reshape与Python NumPy库reshape的差异 背景函数原型MATLAB内置reshapeNumPy库reshape 差异分析解决方法 背景 最近在编程时遇到一个需求&#xff0c;需要将MATLAB工具箱中的函数用Python NumPy库进行实现&#xff0c;在对实现进行验证的过程中&#x…

Ubuntu23.10下解决C语言调用mysql.h问题

Ubuntu23.10下解决C语言调用mysql.h问题 导语环境准备问题和解决方案总结参考文献 导语 在学习C语言和MySQL的调用的时候遇到包和版本的问题&#xff0c;由于使用的书很老&#xff08;10年的&#xff09;&#xff0c;因此很多MySQL的包已经过时&#xff0c;在查找很多资料和询…

【JAVA毕业设计】基于Vue和SpringBoot的微服务在线教育系统

博主说明&#xff1a;本文项目编号 T 060 &#xff0c;文末自助获取源码 \color{red}{T060&#xff0c;文末自助获取源码} T060&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

Spring Boot编程训练系统:技术实现与案例分析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

在JPA和EJB中用乐观锁解决并发问题

同一条记录不同的用户都有权限修改&#xff0c;如&#xff1a;有一条记录编号为100&#xff0c;有一个字段price&#xff0c;张三修改price的值为200&#xff0c;李时修改其值为300。后修改的会覆盖前修改的&#xff0c;张三在修记录编号为100的记录过程中&#xff0c;中途去了…

在 ASP.NET Core 6.0 中使用 Swagger/OpenAPI 丰富 Web API 文档

示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/89961435 介绍 在选择或尝试与 API 集成之前&#xff0c;大多数开发人员都会查看其 API 文档。保持 API 文档更新以反映软件更改是一项挑战&#xff0c;需要时间和精力。对于 Web API&#xff0c;我们…

tensorflow案例5--基于改进VGG16模型的马铃薯识别,准确率提升0.6%,计算量降低78.07%

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 本次采用VGG16模型进行预测&#xff0c;准确率达到了98.875&#xff0c;但是修改VGG16网络结构&#xff0c; 准确率达到了0.9969&#xff0c;并且计算量…

【二叉树】——

计算布尔二叉树的值 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) …

Spring Boot编程训练系统:构建可扩展的应用

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了编程训练系统的开发全过程。通过分析编程训练系统管理的不足&#xff0c;创建了一个计算机管理编程训练系统的方案。文章介绍了编程训练系统的系统分析部分&…

Java-sec-code-SSRF攻击

Java-sec-code&#xff08;SSRF攻击&#xff09; java-sec-code平台中也内置了SSRF攻击案例&#xff0c;我们来看看SSRF漏洞代码是什么样的。 案例1 直接从url参数接收数据&#xff0c;但是未进行任何检查和校验。 通过调用httpUtil.URLConnection方法&#xff0c;建立URL对…

docker基础:搭建centos7(详见B站泷羽sec)

docker的简单学习&#xff1a; sudo apt-get update //这个命令让系统检查有没有新软件 sudo apt-get install docker.io //安装 Docker sudo docker version //查看是否安装成功&#xff0c;显示docker的版本信息 启用Docker 启…

SwiftUI开发教程系列 - 第十四章:项目实战,创建一个完整的 SwiftUI 应用

在这一章,我们将创建一个简单的任务管理应用,名为 MyTasks,用户可以添加、删除和编辑任务,查看完成状态,并实现数据的本地持久化。应用的核心功能包括: 查看任务列表,支持删除和编辑。添加新任务,支持设置任务标题和完成状态。管理任务状态,可以将任务标记为“完成”或…

RNN(循环神经网络)详解

1️⃣ RNN介绍 前馈神经网络&#xff08;CNN&#xff0c;全连接网络&#xff09;的流程是前向传播、反向传播和参数更新&#xff0c;存在以下不足&#xff1a; 无法处理时序数据&#xff1a;时序数据长度一般不固定&#xff0c;而前馈神经网络要求输入和输出的维度是固定的&a…