在极狐 GitLab/GitLab 内部,我们用 Dogfooding 文化来帮助更好地理解产品、解决痛点以及配置错误,构建一个更高效、功能更丰富、用户体验更好的平台。本文将聚焦在「API 模糊测试」的 Dogfooding 实践上。
Web API 模糊测试(Web API Fuzz Testing)主要通过生成大量随机但符合一定语法规则的输入,来对 Web API 进行测试。这种 “随机输入” 可能会触发 API 的一些意料之外的执行路径或错误,从而发现 API 设计或实现中的某些漏洞或错误。
通过分析这些错误,可以发现缺陷和潜在安全问题,而这些问题可能是聚焦特定漏洞的安全扫描工具所遗漏的。极狐 GitLab/GitLab Web API 测试是其他安全手段,如静态应用程序安全测试(SAST)、动态应用程序安全测试(DAST)等的有效补充。
运行 Web API 模糊测试分析器,需具备以下条件之一:
在 API 模糊测试项目初始阶段,API Vision 工作组也在研究「如何在 OpenAPI 规范中自动生成 GitLab REST API endpoint」。因为 GitLab 使用 Grape API 框架,我们已经识别并测试了 grape-swagger gem,它可以基于既有 grape 注释自动生成 OpenAPI v2 规范。
例如,以下 API 端点代码:
Class.new(Grape::API) do
format :json
desc 'This gets something.'
get '/something' do
{ bla: 'something' }
end
add_swagger_documentation
end
会被 grape-swagger 解析为:
{
// rest of OpenAPI v2 specification
…
"paths": {
"/something": {
"get": {
"description": "This gets something.",
"produces": [
"application/json"
],
"operationId": "getSomething",
"responses": {
"200": {
"description": "This gets something."
}
}
}
}
}
}
然而,由于有 2000 多个不同的需求和格式的 API 操作,需要做大量额外工作来解决不满足 grape-swagger 需求或 OpenAPI 格式的边缘例子。一个最简单的例子就是接受文件参数的 API 端点,比如上传指标图片端点(metric image endpoint)。极狐 GitLab/GitLab 使用 Workhorse 智能反向代理来处理 “大型” HTTP 请求,如文件上传。
在这种情况下,文件参数必须是 WorkhorseFile 类型:
namespace ':id/issues/:issue_iid/metric_images' do
…
desc 'Upload a metric image for an issue' do
success Entities::IssuableMetricImage
end
params do
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded'
optional :url, type: String, desc: 'The url to view more metric info'
optional :url_text, type: String, desc: 'A description of the image or URL'
end
post do
require_gitlab_workhorse!
因为 grape-swagger 并不能识别 WorkhorseFile 对应的 OpenAPI 类型,它会将该参数从输出中排除。我们通过在生成过程中添加特定的 grape-swagger 文档来修复该问题:
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: \
'The image file to be uploaded', documentation: { type: 'file' }
但是,并不是所有边缘例子都能够在 grape 注释中通过简单匹配替换来解决。比如,Ruby on Rails 主要支持通配符片段参数,像路由 books/*section/:title
会匹配到 books/some/section/last-words-a-memoir
。
此外,URL 将会被解析,以便成 section
路径参数的值为 some/section
且 title
路径参数的值为 last-words-a-memoir
。
当前,grape-swagger 不能将这些通配片段识别为路径参数,上述路由会生成:
"paths": {
"/api/v2/books/*section/{title}": {
"get": {
...
"parameters": [
{
"in": "query", "name": "*section"
...
}
}
而不是期望的:
"paths": {
"/api/v2/books/{section}/{title}": {
"get": {
...
"parameters": [
{
"in": "path", "name": "section"
...
}
}
因此,我们依旧需要为 grape-swagger 打几次补丁。目前,已经可以实现为绝大多数端点自动生成 OpenAPI 规范。
有了 OpenAPI 规范,现在可以开始 API 模糊测试了。极狐 GitLab/GitLab 使用 Review App 功能为某些特性变更生成测试环境,提供可用的模糊测试目标。
但对于大量给定端点,不能期望一个标准共享 Runner 能在单个 Job 中完成模糊测试。Web API 模糊测试文档包含了性能调优部分,推荐以下操作:
通过使用一个专用模糊测试 Runner 来实现。例如大型计划中的模糊测试工作流,特别是选择了 Long-100 模糊测试配置文件。
通过检查 Job 日志中每个操作所花费的时间,借此排除慢操作。在这个过程中,还可以识别其他需要排除的端点,例如过早结束模糊测试会话的撤销令牌端点。
由于 OpenAPI 格式要求,将测试拆分为多个 Job 花费了最多的精力。
每个 OpenAPI 文档都包含了一系列必填对象和字段,因此不仅仅是根据固定行数进行简单拆分,每个操作还对定义对象中的条目有依赖,需要确保在拆分 OpenAPI 规范时,需要包含端点所需条目。因此,我们编写了一个快速脚本,用来将自测试环境中的实际数据填入示例参数数据,比如项目 ID。
虽然可以在本地运行这些脚本,将拆分作业和 OpenAPI 规范推送到存储库,但每次更新原始 OpenAPI 规范时都会产生大量更改。因此,我们调整了工作流以使用动态生成的子管道,这些管道将在 CI 作业中拆分 OpenAPI 文档,然后为每个拆分文档生成一个包含作业的子管道。这使得迭代变得容易许多而且更加敏捷。我们已经将脚本和流水线配置进行了上传,欢迎参阅。
通过调整并行作业的数量和模糊测试配置,最终我们在可接受的时间范围内,实现了相当全面的模糊测试会话。
完成模糊测试后,现在需要面对数百个发现结果。不同于检测特定漏洞的 DAST 分析器,Web API 模糊测试找寻测试非期望的行为和错误,它们不一定是漏洞。这也就是为什么由 API 模糊测试分析器发现的错误,严重等级会被标记为"Unknow",等待更深入的分级处理。
幸运的是,Web API 模糊测试会在漏洞页面中将 Postman 集合以制品形式输出。这些集合允许用户快速重复在模糊测试期间触发故障的请求。在模糊测试工作流阶段,建议设置一个应用程序本地实例,以便轻松查看日志和调试特定故障。
很多故障的发生都是因为缺乏对意外输入的处理。我们可以从漏洞报告界面直接创建 Issue,并且如果发现特定故障和之前处理过的故障有相同根因时,可以将此漏洞直接链接到原始 Issue 上。
API 模糊测试 Dogfooding 项目被证明是一项富有成效的实践:
针对工作流依旧还有可以改进的地方。迁移到 OpenAPI v3 可以提高 endpoint 的覆盖率;安全团队也在写一个 HAR Recorder 的工具来帮助实时生成 HAR 文件,而不仅仅依赖静态文档等。
对于已经实施多层静态和动态检查并希望采取进一步措施来增加覆盖率的团队,建议尝试将 Web API 模糊测试,以此来验证假设并发现代码中的 “unknow unkonw”。