-
-
[原创]CVE-2024-44337--手把手教你go-fuzz模糊测试引擎如何进行漏洞挖掘
-
发表于: 2天前 1765
-
本文将探讨如何利用模糊测试工具go-fuzz,成功发现并分析了gomarkdown项目中的一个重要漏洞,即CVE-2024-44337。该漏洞涉及输入处理不当,能够被恶意攻击者利用,造成程序崩溃或拒绝服务。这个漏洞的ID编号已经申请出来了,但是细节还没来的急写上去,这是我挖到的第一个CVE,所以记录分析一下!
go-fuzz模糊测试工具地址:dvyukov/go-fuzz: Randomized testing for Go (github.com)
gomarkdown项目地址:gomarkdown/markdown: markdown parser and HTML renderer for Go (github.com)
CVE发现时的issues:"Program Hanged (Timeout 10 Seconds)" Found Using go-fuzz in gomarkdown/markdown · Issue #311 · gomarkdown/markdown (github.com)
复现源码:Brinmon/CVE-2024-44337: CVE-2024-44337 POC .
CVE-2024-44337简介:
包“github.com/gomarkdown/markdown”是一个 Go 库,用于解析 Markdown 文本并呈现为 HTML。在伪版本 'v0.0.0-20240729232818-a2a9c4f' 对应于提交'a2a9c4f76ef5a5c32108e36f7c47f8d310322252' 之前,在parser/block.go文件的paragraph函数中存在逻辑问题,此漏洞允许远程攻击者通过提供导致无限循环的特制输入来导致拒绝服务(DoS)条件,从而导致程序挂起并无限期地消耗资源。提交'a2a9c4f76ef5a5c32108e36f7c47f8d310322252'包含此问题的修补程序。
该漏洞修复的diff链接:fix infinite loop with empty list definition (fixes #311) · gomarkdown/markdown@a2a9c4f (github.com)
这里的漏洞成因非常微小,就只是一个if条件判断考虑失误造成的!
存在漏洞问修复的代码,代码位置parser/block.go:
这段代码的主要功能是在段落中遇到定义列表项时,判断其是否符合定义列表的语法(是否有冒号 :
),并调用相应的函数 p.list
来处理列表条目。如果成功解析出定义列表,函数返回定义列表的结束位置,确保段落处理逻辑能够正确跳过定义列表部分继续解析。
在未修复的代码中,调用 p.list()
方法后会立即返回 prev + listLen
,但是没有验证 listLen
是否有效(即是否大于 0)。
漏洞修复后的代码:
如果 p.list()
返回 0 或者其他异常值,程序没有足够的检查机制来处理这种情况,这可能导致程序进入一个无限循环或者挂起状态,特别是在 p.list()
返回不合适的长度时。
所以这里的解决方案是添加有效性检查:在调用 p.list()
之后,通过检查 listLen
是否为正数来决定是否返回,避免了无效长度引发的挂起问题。
导致无限挂起的原因:
假如 p.list()
方法返回的 listLen
为 0,那么执行 return prev + listLen
的结果会是 return prev
。这意味着下次调用同样的代码时,prev
不变,从而可能造成循环重复调用 p.list()
,并且永远无法退出,导致程序进入无限挂起状态。
环境:Ubuntu20
配置go语言环境:
通过命令行安装fuzz工具:
编译使用教程:go语言模糊测试(一):go-fuzz - FreeBuf网络安全行业门户
源码安装:
最后编译运行验证是否魔改成功!
成功修改go-fuzz源码:
Go-fuzz 是一个用于 Go 包的覆盖率引导的模糊测试解决方案,主要适用于解析复杂输入的包,对强化处理来自潜在恶意用户输入的系统特别有用。它可以帮助发现许多其他测试方法难以发现的错误,且使用成本低、无偏见。完全随机的输入通常难以发现很多错误,而 Go-fuzz 采用覆盖率引导的模糊测试方法,通过收集初始输入语料库并随机变异输入,根据代码覆盖情况添加新的输入到语料库,从而提高发现错误的效率。
go-fuzz 是一个专门为 Go 语言编写的模糊测试工具。它的模块结构如下:
1.Main Package (main.go
)
功能:负责初始化和启动模糊测试引擎,解析命令行参数,管理模糊测试的主循环。
关键函数:main
: 程序入口点,负责解析命令行参数并启动模糊测试。
2.Fuzzing Engine (engine.go
)
功能:核心模糊测试引擎,负责变异输入数据、执行目标函数并收集覆盖率信息。
关键函数:Fuzz
: 主要的模糊测试函数,执行模糊测试的循环。Mutate
: 对输入数据进行变异。
3.Coverage Collection (cover.go
)
功能:收集和报告代码覆盖率信息,以便评估测试用例的有效性。
关键函数:InitCoverage
: 初始化覆盖率收集。RecordCoverage
: 记录覆盖率信息。
4.Instrumentation (instrument.go
)
功能:在目标代码中插入钩子,以便在运行时收集覆盖率信息和其他运行时数据。
关键函数:Instrument
: 在目标代码中插入钩子。
5.Test Case Management (corpus.go
)
功能:管理测试用例集合,包括加载和存储测试用例。
关键函数:LoadCorpus
: 从磁盘加载测试用例集合。AddToCorpus
: 将新的测试用例添加到集合中。
6.Utils (utils.go
)
功能:提供各种实用工具函数,用于日志记录、文件操作等。
关键函数:Log
: 日志记录函数。ReadFile
: 读取文件内容。
7.Persistent Mode (persistent.go
)
功能:支持持久化模糊测试,即在不重启目标程序的情况下持续进行测试。
关键函数:PersistentLoop
: 实现持久化模糊测试的主循环。
8.Configuration and Parameters (config.go
)
功能:管理模糊测试的配置选项和参数。
关键函数:ParseFlags
: 解析命令行参数。LoadConfig
: 加载配置文件。
go-fuzz项目目前实现的特性和功能:
下载目标项目gomarkdown并切换至漏洞版本:
准备模糊测试所需要的语料库,进行模糊测试时都需要进行一个初始的语料库,比较好,也可以不用!
创建一个工作目录来存放预料库和运行目录:
接下来就要开始寻找要测试的函数目标了,这要对这个项目足够了解,或者根据项目的案例来测试重要的函数,比如这个文档:markdown/examples/readme.md 在 master ·GoMarkdown/Markdown --- markdown/examples/readme.md at master · gomarkdown/markdown (github.com)
根据案例学习后发现:Parse() 这个函数比较关键,解析的数据也比较多,大概率存在问题,所以编写测试代码。
在项目中创建一个文件fuzz.go和初始化fuzz:
这样会产生一个文件相当于引入模糊测试库go-fuzz:
编写以下文件,主要是讲模糊测试数据传入Parse(data,nil)函数,运行测试!
编写完成后查看一下:
使用go-fuzz讲fuzz.go编译出俩进行模糊测试:
成功编译出来后会出现一个markdown-fuzz.zip文件我们可以拿出来看看,这个文件可能和声纳和覆盖率有关吧,但是不知道为什么是exe?:
开始整数的模糊测试:
解析输出数据:
来看看运行后产生的工作目录:
corpus:
查看一下刚刚的Crashes产生的文件:
文件解析:
6352b36848220fd923515ee94b6a90237024e28b就算原本导致程序陷入死循环的特定数据,
.output文件是报错时候产生的信息,
.quoted文件是这段特定数据的可见字符编码形式,
讲造成挂起的数据导出进行手动投喂,开始写例子进行手动投喂复现死循环:
运行之后一直挂起,手动停止:
如果相对go-fuzz有更多的了解可以访问这里:
... if p.extensions&DefinitionLists != 0 { if i < len(data)-1 && data[i+1] == ':' { listLen := p.list(data[prev:], ast.ListTypeDefinition, 0, '.') return prev + listLen } } ...
... if p.extensions&DefinitionLists != 0 { if i < len(data)-1 && data[i+1] == ':' { listLen := p.list(data[prev:], ast.ListTypeDefinition, 0, '.') return prev + listLen if listLen > 0 { return prev + listLen } } } ...
ub20@ub20:~$ wget https://go.dev/dl/go1.23.0.linux-amd64.tar.gz ub20@ub20:~$ sudo tar -C /usr/local -xzf ./go1.23.0.linux-amd64.tar.gz #添加环境变量 ub20@ub20:~$ vim ~/.bashrc #内容 # GOROOT:go的安装路径 export GOROOT="/usr/local/go" # GOPATH:go的开发路径(自定义就好) export GOPATH="/home/ub20/gowork" # GOBIN:go工具程序存放路径 export GOBIN=$GOPATH/bin export PATH=$PATH:${GOPATH//://bin:}/bin:/usr/local/go/bin ub20@ub20:~$ source ~/.bashrc #创建工作目录 ub20@ub20:~$ mkdir gowork ub20@ub20:~$ cd ./gowork/ ub20@ub20:~/gowork$ mkdir bin ub20@ub20:~/gowork$ mkdir src ub20@ub20:~/gowork$ mkdir pkg #解决网络代理问题 go env -w GO111MODULE=on go env -w GOPROXY=https://goproxy.io,direct
$ go install github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest
#创建要魔改的go-fuzz mkdir -p $GOPATH/src/github.com/dvyukov/go-fuzz #下载源码,后可以魔改 git clone https://github.com/dvyukov/go-fuzz.git $GOPATH/src/github.com/dvyukov/go-fuzz #进入go-fuzz目录进行初始化 #进入源码目录配置go.mod cd $GOPATH/src/github.com/dvyukov/go-fuzz go mod init #安装go-fuzz所需依赖 go get github.com/elazarl/go-bindata-assetfs go get github.com/stephens2424/writerset go get golang.org/x/tools/go/packages
cd $GOPATH/src/github.com/dvyukov/go-fuzz #安装go-fuzz的编译工具 ub20@ub20:~/gowork/src/github.com/dvyukov/go-fuzz$ go build -o go-fuzz-build ./go-fuzz-build ub20@ub20:~/gowork/src/github.com/dvyukov/go-fuzz$ ./go-fuzz-build/go-fuzz-build --help #安装go-fuzz的运行工具 ub20@ub20:~/gowork/src/github.com/dvyukov/go-fuzz$ go build -o go-fuzz ./go-fuzz ub20@ub20:~/gowork/src/github.com/dvyukov/go-fuzz$ ./go-fuzz/go-fuzz --help #将两个工具安装至环境变量 ub20@ub20:~/gowork/src/github.com/dvyukov/go-fuzz$ go install ./go-fuzz ub20@ub20:~/gowork/src/github.com/dvyukov/go-fuzz$ go install ./go-fuzz-build
ub20@ub20:~$ git clone https://github.com/gomarkdown/markdown.git 正克隆到 'markdown'... remote: Enumerating objects: 4880, done. remote: Counting objects: 100% (700/700), done. remote: Compressing objects: 100% (299/299), done. remote: Total 4880 (delta 442), reused 506 (delta 383), pack-reused 4180 (from 1) 接收对象中: 100% (4880/4880), 1.87 MiB | 2.25 MiB/s, 完成. 处理 delta 中: 100% (3155/3155), 完成. ub20@ub20:~$ git check a2a9c4f76ef5a5c32108e36f7c47f8d310322252^C ub20@ub20:~$ cd ./markdown/ ub20@ub20:~/markdown$ git checkout a2a9c4f76ef5a5c32108e36f7c47f8d310322252 注意:正在切换到 'a2a9c4f76ef5a5c32108e36f7c47f8d310322252'。 ... HEAD 目前位于 a2a9c4f fix infinite loop with empty list definition (fixes #311)