用 govulncheck 找出并修复 Go 项目的脆弱依赖

来源:Go 文档团队,Tutorial: Find and fix vulnerable dependencies with govulncheck。本文为中文翻译整理,保留原教程全部步骤、漏洞判断和关键输出,并补充明确的版本与安全边界。

依赖树里出现已知漏洞,并不等于程序一定会走到有漏洞的函数。govulncheck 先将依赖与已知漏洞信息对照,再分析程序中通往相关函数的直接或间接调用,让报告更聚焦于需要优先处理的问题。这个教程用一个解析语言标签的小程序,演示从扫描、判断影响到升级与复扫的完整过程。

阅读前先确认:原文为了教学,故意把 golang.org/x/text 降到存在已知漏洞的 v0.3.5。该步骤只能在无真实数据、无凭据、无生产目标的隔离教学环境理解或复现,绝不能让现有项目照着降级。原文使用 Go 1.20.3 和 2023 年漏洞库生成的输出;它们是历史材料,不是本文执行结果。v0.3.8 只表示修复了案例中的问题,不是今天的安全版本推荐。

govulncheck先将依赖匹配到已知漏洞,再按调用路径区分实际可达与仅导入,结合业务影响决定升级或移除调用,最后重新扫描。
编者绘制:从依赖漏洞到实际调用,再到修复与复扫。无报告不等于无漏洞。

准备条件

原教程建议使用最新版本的 Go,并准备代码编辑器和终端。Go 可在 Linux、macOS 的常见终端以及 Windows 的 PowerShell 或 cmd 中使用。版本越旧,扫描时越可能额外报告标准库已知漏洞。

下面依次完成四件事:建立带脆弱依赖的样例模块;安装并运行 govulncheck;评估报告;升级依赖并重新扫描。本文未执行任何安装、降级、扫描或样例程序。

第一步:创建样例模块

在独立的教学工作目录里建立 vuln-tutorial,初始化模块。原文以用户主目录为起点;为避免与真实项目混淆,本文将起点明确限定为隔离教学目录。

mkdir vuln-tutorial
cd vuln-tutorial
go mod init vuln.tutorial

随后创建 main.go,写入以下完整程序:

package main

import (
        "fmt"
        "os"

        "golang.org/x/text/language"
)

func main() {
        for _, arg := range os.Args[1:] {
                tag, err := language.Parse(arg)
                if err != nil {
                        fmt.Printf("%s: error: %v\n", arg, err)
                } else if tag == language.Und {
                        fmt.Printf("%s: undefined\n", arg)
                } else {
                        fmt.Printf("%s: tag %s\n", arg, tag)
                }
        }
}

程序把命令行中每个参数当作语言标签,逐个输出解析错误、未定义标签,或成功解析后的标签。它直接调用 language.Parse,这条调用路径稍后会成为漏洞报告的重要证据。

第二步:补齐依赖,并引入教程所需的旧版本

在该目录运行 go mod tidy,让 Go 根据源码填写模块依赖:

go mod tidy

原文记录的输出如下。这是当时的解析结果;今天执行不会保证仍解析到 v0.9.0。

go: finding module for package golang.org/x/text/language
go: downloading golang.org/x/text v0.9.0
go: found golang.org/x/text/language in golang.org/x/text v0.9.0

原文随后检查 go.mod:

module vuln.tutorial

go 1.20

require golang.org/x/text v0.9.0

为了让漏洞分析有可观察的对象,教程刻意改用 v0.3.5:

# 历史教学步骤:只供隔离样例,禁止在真实项目中照此降级
go get golang.org/x/text@v0.3.5

原文的降级输出与修改后的模块文件分别为:

go: downgraded golang.org/x/text v0.9.0 => v0.3.5
module vuln.tutorial

go 1.20

require golang.org/x/text v0.3.5

编者注:这里既不要求读者部署脆弱程序,也不提供触发漏洞的攻击输入。保留旧版本号是为了理解已知漏洞、版本范围与调用路径之间的关系。依赖下载和安装会访问网络,应由读者在获准的实验环境自行评估;本轮没有执行。

第三步:安装并运行 govulncheck

原文通过 go install 安装命令行工具:

go install golang.org/x/vuln/cmd/govulncheck@latest

随后在要分析的模块目录,也就是这里的 vuln-tutorial 中执行:

govulncheck ./...

./... 指当前目录及其子包范围。下面完整保留原教程示例输出的实质内容,含工具链、漏洞库时间、受影响项和信息项;它不是本文运行记录。当前工具版本可能改变报告格式、扫描范围或检出结果,“experimental”也是历史输出中的用词。

govulncheck is an experimental tool. Share feedback at https://go.dev/s/govulncheck-feedback.

Using go1.20.3 and govulncheck@v0.0.0 with
vulnerability data from https://vuln.go.dev (last modified 2023-04-18 21:32:26 +0000 UTC).

Scanning your code and 46 packages across 1 dependent module for known vulnerabilities...
Your code is affected by 1 vulnerability from 1 module.

Vulnerability #1: GO-2021-0113
  Due to improper index calculation, an incorrectly formatted
  language tag can cause Parse to panic via an out of bounds read.
  If Parse is used to process untrusted user inputs, this may be
  used as a vector for a denial of service attack.

  More info: https://pkg.go.dev/vuln/GO-2021-0113

  Module: golang.org/x/text
    Found in: golang.org/x/text@v0.3.5
    Fixed in: golang.org/x/text@v0.3.7

    Call stacks in your code:
      main.go:12:29: vuln.tutorial.main calls golang.org/x/text/language.Parse

=== Informational ===

Found 1 vulnerability in packages that you import, but there are no call
stacks leading to the use of this vulnerability. You may not need to
take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
for details.

Vulnerability #1: GO-2022-1059
  An attacker may cause a denial of service by crafting an
  Accept-Language header which ParseAcceptLanguage will take
  significant time to parse.
  More info: https://pkg.go.dev/vuln/GO-2022-1059
  Found in: golang.org/x/text@v0.3.5
  Fixed in: golang.org/x/text@v0.3.8

第四步:把“依赖有漏洞”和“程序会调用”分开判断

上述报告指出两个漏洞,但两者对样例的意义不同:

漏洞 问题与样例的关系 原文给出的修复版本
GO-2021-0113 language.Parse 的索引计算不当,处理格式异常的语言标签时可能越界读取并 panic。样例直接调用该函数,报告给出 main.go:12:29 的调用位置。 v0.3.7
GO-2022-1059 特制的 Accept-Language 头可能让 ParseAcceptLanguage 解析耗时显著增加。脆弱代码存在于所用模块,但样例没有直接或间接调用相关脆弱函数,因此原输出将其列为 Informational。 v0.3.8

评估时先阅读漏洞描述,确认它是否适用于当前代码和业务输入。如果描述还不够,继续查报告中的 “More info” 链接。按照原教程的假设,这个程序应当能承受不可信输入,而拒绝服务也是需要防范的风险,所以 GO-2021-0113 应当修复。GO-2022-1059 对当前样例的优先级较低,因为报告没有找到通往脆弱函数的调用路径。

编者注:“未发现调用路径”是工具在当前代码、构建条件和已知漏洞数据下的分析结果,不能外推为任意运行场景都不可触发,更不能当作整个模块安全的证明。程序、构建标志或依赖发生变化后,结论应重新评估。

第五步:选择修复动作

原教程列出两类处置方法:

  • 升级到已修复版本。存在可用修复时,通过更新模块消除已知脆弱依赖。
  • 停止使用有漏洞的符号。移除相关调用,寻找替代实现,或自行实现所需功能;这通常还需要额外的正确性与安全验证。

这个例子离不开 Parse,而且已有修复,所以选择升级。只消除 GO-2021-0113 时,原报告列出的起始修复版本是 v0.3.7;考虑到 GO-2022-1059 与它位于同一个模块,直接升到 v0.3.8 就能同时消除原案例中的两个已知问题。这说明“降低优先级”不等于“必须保留”:能用一次兼容升级顺便解决的问题,仍然可以一起处理。

第六步:升级并再次扫描

原文使用下面的版本演示升级。它是历史案例命令,不是当前生产推荐版本:

# 原教程的历史修复版本,仅用于理解案例
go get golang.org/x/text@v0.3.8

原文输出:

go: upgraded golang.org/x/text v0.3.5 => v0.3.8

原文也说明可以选择 latest 或 v0.3.8 之后的其他版本。实际项目应根据当前漏洞报告、支持范围和兼容性选定版本,验证后再提交依赖变更,不能把旧教程中的“Fixed in”理解为永久有效的安全基线。

升级完成后重新运行:

govulncheck ./...

原教程保留的复扫输出为:

govulncheck is an experimental tool. Share feedback at https://go.dev/s/govulncheck-feedback.

Using go1.20.3 and govulncheck@v0.0.0 with
vulnerability data from https://vuln.go.dev (last modified 2023-04-06 19:19:26 +0000 UTC).

Scanning your code and 46 packages across 1 dependent module for known vulnerabilities...
No vulnerabilities found.

注意,原文两次示例输出中的漏洞库时间并不相同,后一个甚至早于前一个。因此这些输出应作为教程的历史展示阅读,不能当成同一数据库快照下的受控实验。本轮没有复现它们。

这个流程的价值在于形成循环:定期扫描依赖,依据调用路径和业务影响确定优先级,选择修复办法,然后对更新后的代码复扫。还应保留工具版本、Go 版本、模块锁定信息及报告,便于以后理解当时结论。

代码审查与方法边界

静态查看样例可确认:命令行参数被直接交给 language.Parse,没有经过 shell 执行;示例中未见硬编码密码或访问令牌。案例实际问题是脆弱库处理不可信输入可能触发 panic,而不是 shell 注入。没有发现其他问题不等于不存在其他漏洞。

govulncheck 关注已知漏洞及可达性,不覆盖全部未知缺陷、业务授权错误或所有运行期行为;“No vulnerabilities found”只说明该次分析没有报告匹配的已知问题。依赖升级后仍需做适当的功能与安全回归。本文所有代码均为静态核查,未进行这些测试。

来源、归属与许可

原作:Go 文档团队,govulncheck 教程。延伸资料:govulncheck 命令文档、Go 漏洞管理介绍。正文与图示说明由未完纪翻译整理,变更包括中文表述、明确隔离边界和历史输出标记。

依据 Go 站点版权声明,除另有说明外,正文采用 CC BY 4.0,代码采用 BSD 许可。Copyright 2009 The Go Authors. 代码许可及免责声明全文保存于 go-BSD.txt。原创配图是分析流程示意,不是实测结果或漏洞扫描截图。

Go 代码 BSD 许可全文

Copyright 2009 The Go Authors.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
   * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容