こんにちは、プロダクト開発部コアグループの井上です。
コアグループでは、次世代ECの開発を行っています。
今回はgovulncheckとGitHub ActionsをつかってGoの脆弱性チェックを自動化した取り組みについて紹介します。
govulncheck
govulncheckは、Goプロジェクトの依存関係とソースコードを分析し、既知の脆弱性を検出するツールです。コマンドラインから簡単に実行することができます。
今回はCVE-2024-24789を含むこちらのサンプルを想定して書いていきます。
> go version go version go1.22.3 windows/amd64
vulnsample/ ├── cmd/ │ └── main.go ├── util.go └── go.mod
module vulnsample go 1.22
package main import ( "fmt" "os" "log/slog" "vulnsample" ) func main() { slog.Info("Start") f, err := os.Open("path/to/zipfile.zip") if err != nil { panic(err) } defer f.Close() fi, err := f.Stat() if err != nil { panic(err) } result, err := vulnsample.Unzip(f, fi.Size()) if err != nil { panic(err) } fmt.Println(result) }
package vulnsample import ( "archive/zip" "io" ) func Unzip(zipStream io.ReaderAt, size int64) (map[string][]byte, error) { zipReader, err := zip.NewReader(zipStream, size) if err != nil { return nil, err } contents := make(map[string][]byte) for _, zipFile := range zipReader.File { f, err := zipFile.Open() if err != nil { return nil, err } defer f.Close() data, err := io.ReadAll(f) if err != nil { return nil, err } contents[zipFile.Name] = data } return contents, nil }
実行方法
go install golang.org/x/vuln/cmd/govulncheck@latest
> govulncheck ./... === Symbol Results === Vulnerability #1: GO-2024-2888 Mishandling of corrupt central directory record in archive/zip More info: https://pkg.go.dev/vuln/GO-2024-2888 Standard library Found in: archive/zip@go1.22.3 Fixed in: archive/zip@go1.22.4 Example traces found: #1: util.go:10:33: vulnsample.Unzip calls zip.NewReader Your code is affected by 1 vulnerability from the Go standard library. This scan also found 0 vulnerabilities in packages you import and 2 vulnerabilities in modules you require, but your code doesn't appear to call these vulnerabilities. Use '-show verbose' for more details.
-json
オプションを利用することで、結果をjson形式で得ることができます。
{ "config": { "protocol_version": "v1.0.0", "scanner_name": "govulncheck", "scanner_version": "v1.1.2", "db": "https://vuln.go.dev", "db_last_modified": "2024-07-02T20:11:00Z", "go_version": "go1.22.3", "scan_level": "symbol", "scan_mode": "source" } } { "progress": { "message": "Scanning your code and 58 packages across 1 dependent module for known vulnerabilities..." } } { "progress": { "message": "Fetching vulnerabilities from the database..." } } { "osv": { "schema_version": "1.3.1", "id": "GO-2021-0067", "modified": "2024-05-20T16:03:47Z", "published": "2021-04-14T20:04:52Z", "aliases": [ "CVE-2021-27919" ], "summary": "Panic when opening archives in archive/zip", "details": "Using Reader.Open on an archive containing a file with a path prefixed by \"../\" will cause a panic due to a stack overflow. If parsing user supplied archives, this may be used as a denial of service vector.", "affected": [ { "package": { "name": "stdlib", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "1.16.0-0" }, { "fixed": "1.16.1" } ] } ], "ecosystem_specific": { "imports": [ { "path": "archive/zip", "symbols": [ "toValidName" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/300489" }, { "type": "FIX", "url": "https://go.googlesource.com/go/+/cd3b4ca9f20fd14187ed4cdfdee1a02ea87e5cd8" }, { "type": "REPORT", "url": "https://go.dev/issue/44916" }, { "type": "WEB", "url": "https://groups.google.com/g/golang-announce/c/MfiLYjG-RAw/m/zzhWj5jPAQAJ" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2021-0067", "review_status": "REVIEWED" } } } { "osv": { "schema_version": "1.3.1", "id": "GO-2024-2888", "modified": "2024-06-04T22:48:55Z", "published": "2024-06-04T22:48:55Z", "aliases": [ "CVE-2024-24789" ], "summary": "Mishandling of corrupt central directory record in archive/zip", "details": "The archive/zip package's handling of certain types of invalid zip files differs from the behavior of most zip implementations. This misalignment could be exploited to create an zip file with contents that vary depending on the implementation reading the file. The archive/zip package now rejects files containing these errors.", "affected": [ { "package": { "name": "stdlib", "ecosystem": "Go" }, "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "1.21.11" }, { "introduced": "1.22.0-0" }, { "fixed": "1.22.4" } ] } ], "ecosystem_specific": { "imports": [ { "path": "archive/zip", "symbols": [ "NewReader", "OpenReader", "findSignatureInBlock" ] } ] } } ], "references": [ { "type": "FIX", "url": "https://go.dev/cl/585397" }, { "type": "REPORT", "url": "https://go.dev/issue/66869" }, { "type": "WEB", "url": "https://groups.google.com/g/golang-announce/c/XbxouI9gY7k/m/TuoGEhxIEwAJ" } ], "credits": [ { "name": "Yufan You (@ouuan)" } ], "database_specific": { "url": "https://pkg.go.dev/vuln/GO-2024-2888", "review_status": "REVIEWED" } } } { "progress": { "message": "Checking the code against the vulnerabilities..." } } { "finding": { "osv": "GO-2024-2887", "fixed_version": "v1.22.4", "trace": [ { "module": "stdlib", "version": "v1.22.3" } ] } } { "finding": { "osv": "GO-2024-2888", "fixed_version": "v1.22.4", "trace": [ { "module": "stdlib", "version": "v1.22.3" } ] } } { "finding": { "osv": "GO-2024-2963", "fixed_version": "v1.22.5", "trace": [ { "module": "stdlib", "version": "v1.22.3" } ] } } { "finding": { "osv": "GO-2024-2888", "fixed_version": "v1.22.4", "trace": [ { "module": "stdlib", "version": "v1.22.3", "package": "archive/zip" } ] } } { "finding": { "osv": "GO-2024-2888", "fixed_version": "v1.22.4", "trace": [ { "module": "stdlib", "version": "v1.22.3", "package": "archive/zip", "function": "NewReader", "position": { "filename": "src/archive/zip/reader.go", "offset": 3044, "line": 106, "column": 6 } }, { "module": "vulnsample", "package": "vulnsample", "function": "Unzip", "position": { "filename": "util.go", "offset": 189, "line": 10, "column": 33 } } ] } }
osv
OSV (Open Software Vulnerabilities)
は、Googleが管理しているオープンソースソフトウェアの脆弱性を追跡するためのデータベースで、
出力結果として得られるosv
はそのフォーマット(OSV Schema)に沿ったGoの脆弱性情報です。
govulncheckの実装
を見ると、スキャン対象のモジュールに影響がありそうなものがosv
として出力されているようでした。
※記事に乗せるには量が多すぎるため、上記のサンプルでは削っています。
affected.ranges
を見ることで修正バージョンが分かります。
finding
スキャンの結果見つかった脆弱性はfinding
として出力されます。
symbolレベルでスキャンを実行した場合(デフォルト)、trace
内に影響を受けるコードの情報が入ってきます。
今回はutil.goの10行目でzip.NewReader
を使用しているためGO-2024-2888
の影響があるということが分かります。
今回作ったもの
GitHub Actionsであればアクションが公開されているため、簡単にCIに組み込むことができますが、
今回はISSUEの作成まで自動で行いたかったので、govulncheck ./... -json
の結果を自前でパースすることにしました。
jsonのパース
govulncheck ./... -json
の結果はjson.Unmarshal
ではパースすることはできません。
当初はbufio.Scanner.Scan()
を使って力業でパースしましたが、json.Decoder
を使用することで簡単にパースすることができます。
package main import ( "encoding/json" "fmt" "os" "path/filepath" ) func main() { home, _ := os.UserHomeDir() vulncheckResult, err := os.Open(filepath.Join(home, "workspace", "govulncheck.json")) if err != nil { panic(err) } result := make([]map[string]any, 0) dec := json.NewDecoder(vulncheckResult) for dec.More() { m := make(map[string]any) if err := dec.Decode(&m); err != nil { panic(err) } if len(m) > 0 { fmt.Println(m) result = append(result, m) } } _ = result }
map[config:map[db:https://vuln.go.dev db_last_modified:2024-07-02T20:11:00Z go_version:go1.22.3 protocol_version:v1.0.0 scan_level:symbol scan_mode:source scanner_name:govulncheck scanner_version:v1.1.2]] map[progress:map[message:Scanning your code and 58 packages across 1 dependent module for known vulnerabilities...]] map[progress:map[message:Fetching vulnerabilities from the database...]] map[osv:map[affected:[map[ecosystem_specific:map[imports:[map[path:archive/zip symbols:[toValidName]]]] package:map[ecosystem:Go name:stdlib] ranges:[map[events:[map[introduced:1.16.0-0] map[fixed:1.16.1]] type:SEMVER]]]] aliases:[CVE-2021-27919] database_specific:map[review_status:REVIEWED url:https://pkg.go.dev/vuln/GO-2021-0067] details:Using Reader.Open on an archive containing a file with a path prefixed by "../" will cause a panic due to a stack overflow. If parsing user supplied archives, this may be used as a denial of service vector. id:GO-2021-0067 modified:2024-05-20T16:03:47Z published:2021-04-14T20:04:52Z references:[map[type:FIX url:https://go.dev/cl/300489] map[type:FIX url:https://go.googlesource.com/go/+/cd3b4ca9f20fd14187ed4cdfdee1a02ea87e5cd8] map[type:REPORT url:https://go.dev/issue/44916] map[type:WEB url:https://groups.google.com/g/golang-announce/c/MfiLYjG-RAw/m/zzhWj5jPAQAJ]] schema_version:1.3.1 summary:Panic when opening archives in archive/zip]] map[osv:map[affected:[map[ecosystem_specific:map[imports:[map[path:archive/zip symbols:[NewReader OpenReader findSignatureInBlock]]]] package:map[ecosystem:Go name:stdlib] ranges:[map[events:[map[introduced:0] map[fixed:1.21.11] map[introduced:1.22.0-0] map[fixed:1.22.4]] type:SEMVER]]]] aliases:[CVE-2024-24789] credits:[map[name:Yufan You (@ouuan)]] database_specific:map[review_status:REVIEWED url:https://pkg.go.dev/vuln/GO-2024-2888] details:The archive/zip package's handling of certain types of invalid zip files differs from the behavior of most zip implementations. This misalignment could be exploited to create an zip file with contents that vary depending on the implementation reading the file. The archive/zip package now rejects files containing these errors. id:GO-2024-2888 modified:2024-06-04T22:48:55Z published:2024-06-04T22:48:55Z references:[map[type:FIX url:https://go.dev/cl/585397] map[type:REPORT url:https://go.dev/issue/66869] map[type:WEB url:https://groups.google.com/g/golang-announce/c/XbxouI9gY7k/m/TuoGEhxIEwAJ]] schema_version:1.3.1 summary:Mishandling of corrupt central directory record in archive/zip]] map[progress:map[message:Checking the code against the vulnerabilities...]] map[finding:map[fixed_version:v1.22.4 osv:GO-2024-2887 trace:[map[module:stdlib version:v1.22.3]]]] map[finding:map[fixed_version:v1.22.4 osv:GO-2024-2888 trace:[map[module:stdlib version:v1.22.3]]]] map[finding:map[fixed_version:v1.22.5 osv:GO-2024-2963 trace:[map[module:stdlib version:v1.22.3]]]] map[finding:map[fixed_version:v1.22.4 osv:GO-2024-2888 trace:[map[module:stdlib package:archive/zip version:v1.22.3]]]] map[finding:map[fixed_version:v1.22.4 osv:GO-2024-2888 trace:[map[function:NewReader module:stdlib package:archive/zip position:map[column:6 filename:src/archive/zip/reader.go line:106 offset:3044] version:v1.22.3] map[function:Unzip module:vulnsample package:vulnsample position:map[column:33 filename:util.go line:10 offset:189]]]]] Process finished with the exit code 0
ISSUEの作成
パース結果をもとに、リポジトリのコードに影響があるtrace.position
が存在する脆弱性を集計し、ISSUE用のmarkdownに成形して、actions/github-script
でISSUEを作成しています。
余談ですがaliasに入ってくるCVEは、CVE-2024-24786
などをそのままISSUE張り付けただけでリンクになって便利でした。
おわりに
今回は脆弱性があっても、対象の関数などを使用していないなどで影響がない場合はISSUEを作成しないようにしましたが、結局別チームが別途拾って対応することになってしまいました。
修正版リリースから最速で対応できるよう、findingがある場合は無条件にISSUEが作成されるように変更予定です。
また、現在は一度出た脆弱性を保存する仕組みを用意していない為、重複したISSUEが作られてしまわないようにスキャン頻度を週一回にしています。 毎日実行しても問題ないようこの点も改善していく予定です。
皆さんも手軽に脆弱性チェックができるgovulncheck
を活用してみてください。