Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I’ll earn a commission. Please notice your final price is not affected at all by using those links.
Welcome to another post part of the series covering Quality Attributes / Non-Functional Requirements, this time I’m talking about Security
specifically the just announced Vulnerability Management support by the Go team.
What does Vulnerability Management consist of?
Go’s Vulnerability Management support consists of three clear pillars:
- Data Sources, including:
- National Vulnerability Database (NVD),
- GitHub Security Advisory (GHSA),
- Vulnerabilities reported by Go package maintainers, and
- Vulnerabilities fixed by the Go team.
- Vulnerability Database, which consolidates all the original data sources, and
- Tools and Integrations, including:
Vulnerability Database and Package Discovery Site
The Vulnerability Database is reviewed and curated by the Go Security Team, it’s integrated with the Package Discovery Site and currently includes some useful features such as:
- Listing known vulnerability reports by recency,
- Searching for reports,
- Dedicated landing pages for reports, for example GO-2022-0603, and
- My favorite one! Listing known vulnerabilities next to the package, including those in the standard library as well as third party ones:
Detecting vulnerabilities using govulncheck
govulncheck
is a new tool that uses the Vulnerability Database to detect known issues local to your codebase, it’s similar to the tools I covered in Software Architecture in Go: Security - Dependencies, however there are three biggest differentiators:
govulncheck
supports scanning already-compiled binaries to search for vulnerabilities,govulncheck
reports vulnerabilities only if the vulnerable code is in use, andgovulncheck
is free and officially supported by the Go team.
Let’s dig deeper about this new tool.
Installing govulncheck
The code used for this post is available on Github.
govulncheck
requires Go 1.18, to install it run the following:
go install golang.org/x/vuln/cmd/govulncheck@latest
At the moment of writing this post there are no tagged releases however you should still consider versioning it to a concrete commit instead of using
latest
just like I mentioned in Learning Go: Versioning Tools.
Detecting vulnerabilities in compiled binaries
This feature requires the binary to evaluate to be compiled with Go 1.18 or greater and is one of those features I don’t recall seeing anywhere else. Let’s look at the following example:
1package main
2
3import (
4 "fmt"
5 "net/url"
6)
7
8func main() {
9 fmt.Println(url.JoinPath("https://go.dev", "../x")) // https://go.dev/../x
10 fmt.Println(url.JoinPath("https://go.dev/", "../x")) // https://go.dev/x
11}
The example above demonstrates the GO-2022-0988 vulnerability introduced in Go 1.19 and later fixed in Go 1.19.1. To compile both versions we can use docker, for Go 1.19 running the following command will compile a local binary called go119
:
docker run --rm \
-v "$PWD":/govulncheck -w /govulncheck \
-e GOOS=darwin -e GOARCH=amd64 \
golang:1.19.0-buster go build -o go119 .
And running the following command will create a binary called go1191
compiled using Go 1.19.1:
docker run --rm \
-v "$PWD":/govulncheck -w /govulncheck \
-e GOOS=darwin -e GOARCH=amd64 \
golang:1.19.1-buster go build -o go1191 .
Testing the first binary go119
using govulncheck go119
will report the vulnerability:
In contrast when using the second binary go1191
via govulncheck go119
no vulnerabilities will be found:
This feature allows to retroactively evaluate old binaries to determine if immediate upgrade is necessary, however keep in mind that a regular Go upgrade and binary recompilation is recommended from time to time.
Detecting vulnerabilities that actually affect you
Typically vulnerability tools scan the source code to determine whether imported packages include vulnerabilities or not, govulncheck
takes an extra step by inspecting our code to evaluate if in fact the vulnerable code is called, therefore avoiding false negatives and noisy reports.
Let’s look at two examples, both of them are importing a vulnerable package in their go.mod
, reported as GO-2022-0603:
3go 1.19
4
5require gopkg.in/yaml.v3 v3.0.0
First example consists of using the vulnerable code:
7func main() {
8 var t interface{}
9 yaml.Unmarshal([]byte("0: [:!00 \xef"), &t)
10}
Executing govulncheck ./...
correctly reports the vulnerability:
Second example imports the vulnerable package but doesn’t call the affected function:
7func main() {
8 type T struct {
9 F int `yaml:"a,omitempty"`
10 B int
11 }
12
13 yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
14}
In this case executing govulncheck ./...
correctly reports the vulnerability but it indicates we are not using it, this is important because it allows remediation to be prioritized correctly, we definitely need to update the package but there’s no urgent reason to do so, that’s the big difference.
Tools that use go.mod
to determine vulnerabilities won’t make this fine distinction, for example Snyk detects both cases no matter what: using vulnerable code and not using vulnerable code.
Conclusion
This new feature is a very welcome addition to the Go ecosystem, although I don’t think this tool will replace existing ones I do believe it will complement them, I look forward seeing what comes next in future releases.
Recommended reading
If you’re looking to sink your teeth into more Software Architecture, including Security, I recommend the following content:
- Post: Quality Attributes / Non-Functional Requirements
- Post: Software Architecture in Go: Security - Dependencies
- Post: Software Architecture in Go: Security - Preventing SQL Injection
- Book: Hands-On Software Architecture with Golang
- Book: Building Evolutionary Architectures: Support Constant Change - Post
- Book: Practical Cloud Security: A Guide for Secure Design and Deployment