チュートリアル: govulncheck で脆弱な依存関係を見つけて修正する

Govulncheck は、Go プロジェクト内の脆弱な依存関係を見つけて修正するのに役立つ、ノイズの少ないツールです。既知の脆弱性についてプロジェクトの依存関係をスキャンし、コード内のそれらの脆弱性への直接的または間接的な呼び出しを特定することによってこれを行います。

このチュートリアルでは、govulncheck を使用して単純なプログラムの脆弱性をスキャンする方法を学習します。また、脆弱性を優先順位付けして評価し、最も重要なものから修正に集中できるようにする方法も学習します。

govulncheck の詳細については、govulncheck ドキュメントと、Go の脆弱性管理に関するこのブログ記事を参照してください。また、皆様からのフィードバックもお待ちしております。

前提条件

  • Go. このチュートリアルに従うには、最新バージョンの Go を使用することをお勧めします。(インストール手順については、Go のインストールを参照してください。)
  • コードエディタ. お持ちのどのエディタでも問題ありません。
  • コマンドターミナル. Go は Linux および Mac の任意のターミナル、および Windows の PowerShell または cmd でうまく動作します。

このチュートリアルでは、以下の手順を説明します

  1. 脆弱な依存関係を持つサンプル Go モジュールを作成する
  2. govulncheck をインストールして実行する
  3. 脆弱性を評価する
  4. 脆弱な依存関係をアップグレードする

脆弱な依存関係を持つサンプル Go モジュールを作成する

ステップ 1. まず、vuln-tutorial という新しいフォルダーを作成し、Go モジュールを初期化します。(Go モジュールを初めて使用する場合は、go.dev/doc/tutorial/create-module を確認してください。)

たとえば、ホームディレクトリから以下を実行します

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

ステップ 2. 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)
                }
        }
}

このサンプルプログラムは、コマンドライン引数として言語タグのリストを受け取り、各タグについて、正常に解析されたか、タグが未定義であるか、またはタグの解析中にエラーが発生したかを示すメッセージを出力します。

ステップ 3. go mod tidy を実行すると、前のステップで main.go に追加したコードに必要なすべての依存関係が go.mod ファイルに入力されます。

vuln-tutorial フォルダーから実行します

$ go mod tidy

次の出力が表示されます

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

ステップ 4. go.mod ファイルを開き、次のようになっていることを確認します

module vuln.tutorial

go 1.20

require golang.org/x/text v0.9.0

ステップ 5. 既知の脆弱性を含む golang.org/x/text のバージョンを v0.3.5 にダウングレードします。実行します

$ go get golang.org/x/text@v0.3.5

次の出力が表示されます

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

go.mod ファイルは次のようになります

module vuln.tutorial

go 1.20

require golang.org/x/text v0.3.5

それでは、govulncheck を実際に見てみましょう。

govulncheck をインストールして実行する

ステップ 6. go install コマンドで govulncheck をインストールします

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

ステップ 7. 分析したいフォルダー (この場合は vuln-tutorial) から実行します

$ govulncheck ./...

次の出力が表示されます

govulncheck is an experimental tool. Share feedback at https://go.dokyumento.jp/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 を使用していない場合、標準ライブラリから追加の脆弱性が表示される場合があります。

私たちのコードは、脆弱なバージョン (v0.3.5) で golang.org/x/text/languageParse 関数を直接呼び出しているため、1 つの脆弱性 GO-2021-0113 の影響を受けています。

別の脆弱性 GO-2022-1059 は、golang.org/x/text モジュールの v0.3.5 に存在します。ただし、私たちのコードが脆弱な関数を (直接的または間接的に) 呼び出すことがないため、「情報」として報告されます。

それでは、脆弱性を評価し、取るべき行動を決定しましょう。

脆弱性を評価する

a. 脆弱性を評価する。

まず、脆弱性の説明を読み、それが実際にコードとユースケースに適用されるかどうかを判断します。詳細が必要な場合は、「詳細情報」リンクにアクセスしてください。

説明に基づくと、脆弱性 GO-2021-0113 は、信頼できないユーザー入力を処理するために Parse が使用されるとパニックを引き起こす可能性があります。私たちのプログラムが信頼できない入力に耐えることを意図しており、サービス拒否を懸念していると仮定すると、この脆弱性は適用される可能性が高いです。

GO-2022-1059 は、私たちのコードがそのレポートの脆弱な関数を呼び出さないため、私たちのコードには影響しない可能性が高いです。

b. 行動を決定する。

GO-2021-0113 を軽減するには、いくつかの選択肢があります

  • 選択肢 1: 修正済みバージョンにアップグレードする. 利用可能な修正がある場合、モジュールの修正済みバージョンにアップグレードすることで、脆弱な依存関係を削除できます。
  • 選択肢 2: 脆弱なシンボルの使用を停止する. コード内の脆弱な関数へのすべての呼び出しを削除することを選択できます。代替案を見つけるか、自分で実装する必要があります。

この場合、修正が利用可能であり、Parse 関数は私たちのプログラムに不可欠です。依存関係を「修正済み」バージョンである v0.3.7 にアップグレードしましょう。

情報的な脆弱性である GO-2022-1059 の修正は優先順位を下げることにしましたが、GO-2021-0113 と同じモジュールにあり、修正済みバージョンが v0.3.8 であるため、v0.3.8 にアップグレードすることで両方を同時に簡単に削除できます。

脆弱な依存関係をアップグレードする

幸いなことに、脆弱な依存関係のアップグレードは非常に簡単です。

ステップ 8. golang.org/x/text を 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 以降の他のバージョンにアップグレードすることもできたことに注意してください)。

ステップ 9. 再び govulncheck を実行します

$ govulncheck ./...

次の出力が表示されます

govulncheck is an experimental tool. Share feedback at https://go.dokyumento.jp/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.

最後に、govulncheck は脆弱性が見つからなかったことを確認します。

コマンド govulncheck で依存関係を定期的にスキャンすることで、脆弱性を特定、優先順位付け、対処することで、コードベースを保護できます。