はじめに
この記事は、Go 言語 Advent Calendar 2023 シリーズ2の16日目の記事です。
こんにちは。reo です。
今年も気づけばアドベントカレンダーの時期ですね。
社のカレンダーにも参加したので、こちらも宣伝させていただきます。
今回はTrivyのWebAssembly拡張について紹介させていただきます。
前提
- 以下は詳しく触れません。
- WebAssembly の概要
- WebAssemblyランタイム(wazero)について
- Trivyの全体像について
掲載内容は私自身の見解であり、必ずしも所属する企業や組織の立場、戦略、意見を代表するものではありません。1
Trivy とは
公式の README にもあるとおり、コンテナイメージからファイルシステム、Gitリポジトリ、Kubernetes、AWSなど幅広いターゲットに対して、既知の脆弱性(CVE)や機密情報、IaCの設定ミスをスキャンします。
個人的には、コマンドの簡潔さが気に入っています。
サンプルにある通り、コンテナイメージのスキャンはこれだけです。
trivy image python:3.4-alpine
スキャン結果は以下のように出力されます。
advent-calendar-2023-go (debian 12.2)
=====================================
Total: 40 (HIGH: 38, CRITICAL: 2)
┌───────────────────────┬────────────────┬──────────┬──────────────┬───────────────────┬──────────────────┬──────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├───────────────────────┼────────────────┼──────────┼──────────────┼───────────────────┼──────────────────┼──────────────────────────────────────────────────────────────┤
│ git │ CVE-2023-25652 │ HIGH │ affected │ 1:2.39.2-1.1 │ │ git: by feeding specially crafted input to `git apply │
│ │ │ │ │ │ │ --reject`, a path... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-25652 │
│ ├────────────────┤ │ │ ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2023-29007 │ │ │ │ │ git: arbitrary configuration injection when renaming or │
│ │ │ │ │ │ │ deleting a section from a... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-29007 │
├───────────────────────┼────────────────┤ │ │ ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ git-man │ CVE-2023-25652 │ │ │ │ │ git: by feeding specially crafted input to `git apply │
│ │ │ │ │ │ │ --reject`, a path... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-25652 │
│ ├────────────────┤ │ │ ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ │ CVE-2023-29007 │ │ │ │ │ git: arbitrary configuration injection when renaming or │
│ │ │ │ │ │ │ deleting a section from a... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-29007 │
└───────────────────────┴────────────────┴──────────┴──────────────┴───────────────────┴──────────────────┴──────────────────────────────────────────────────────────────┘
TrivyのModules
について
公式ページでも説明されているとおり、この機能は後方互換性を保つことなく変更される可能性があるとのことなので、実運用される場合は注意してください。
Modulesは、Trivyのスキャン結果をWebAssemblyで拡張できる機能です。
この機能がすごいのは、Trivy本体のバイナリを変更せずに、利用側で独自のスキャナーを追加できたり、スキャン結果を上書きできることです。
2023/12時点では、TinyGoのみサポートされています。
TrivyはCGOに依存せずにWebAssemblyを実行するため、wazeroを活用しているそうです。
筆者も、TrivyにWebAssembly拡張機能があることを知ったのは、wazeroの公式ページを見たのがきっかけです。
以下は、Modulesが提供しているインタフェースです。
Analyzer
, PostScanner
のどちらか一方を満たす必要があります。(両方満たすこともできます)
Modules
はいずれのパターンでも実装します。
type Module interface {
Version() int
Name() string
}
type Analyzer interface {
RequiredFiles() []string
Analyze(filePath string) (*serialize.AnalysisResult, error)
}
type PostScanner interface {
PostScanSpec() serialize.PostScanSpec
PostScan(serialize.Results) (serialize.Results, error)
}
Modulesは大きく2つの機能があります。
機能1: Analyzer
Analyzer
インタフェースを実装すると、正規表現にマッチした名前のファイルを独自のルールでスキャンすることができます。
公式のサンプルでは、RequiredFiles()
メソッドで正規表現を以下のように指定しています。
func (WordpressModule) RequiredFiles() []string {
return []string{
`wp-includes\/version.php`,
}
}
上記の場合、ファイルシステムやコンテナにwp-includes/version.php
が存在すればスキャン対象とすることができます。
スキャン対象になったファイルのリストをAnalyze(filePath string)
メソッドにて独自のルールでスキャンすることができます。
公式サンプルでは、以下のようにファイル内に設定されているバージョン情報を抜き出して、値をトリミングします。
$wp_version = '5.7.1';
抜き出した情報を、Trivy本体のスキャン結果に追加することができます。
CustomResources: []serialize.CustomResource{
{
Type: typeWPVersion,
FilePath: filePath, // /usr/src/wordpress/wp-includes/version.php
Data: wpVersion, // 5.7.1
},
},
機能2: PostScanner
PostScanner
インタフェースを実装することで、Trivyのスキャン結果に対して、以下3つの操作の1つを選択して実行できます。
スキャン結果には、前述したAnalyze
メソッドで抜き出した結果も含まれます。
- スキャン結果を受けて、該当の脆弱性について新しい説明を追加する(
Insert
) - スキャン結果を受けて、該当の脆弱性もしくは設定ミスを更新する(
Update
) - スキャン結果を受けて、該当の脆弱性もしくは設定ミスを削除する(
Delete
)
上記の操作は、PostScan(serialize.Results)
メソッドで行います。
公式サンプルでは、Analyze
メソッドでスキャン結果に追加したWordPressのバージョンチェックを行っています。
具体的には、バージョンが 5.7 <= version < 5.7.2
に含まれるかチェックして、該当すれば独自のスキャン結果を追加する処理を行っています。
サンプルのコードはこちら(便宜上、エラーハンドリングや変数定義を省いています)
func (WordpressModule) PostScan(results serialize.Results) (serialize.Results, error) {
affectedVersion, err := version.NewConstraint(">=5.7, <5.7.2")
for _, result := range results {
for _, c := range result.CustomResources {
wpPath = c.FilePath
wpVersion = c.Data.(string)
wasm.Info(fmt.Sprintf("WordPress Version: %s", wpVersion))
ver, err := version.NewVersion(wpVersion)
if err != nil {
return nil, err
}
// NOTE: ver=5.7.1 なので検出される
if affectedVersion.Check(ver) {
vulnerable = true
}
break
}
}
if vulnerable {
// Add CVE-2020-36326 and CVE-2018-19296
results = append(results, serialize.Result{
Target: wpPath,
Class: types.ClassLangPkg,
Type: "wordpress",
Vulnerabilities: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-36326",
PkgName: "wordpress",
InstalledVersion: wpVersion,
FixedVersion: "5.7.2",
Vulnerability: dbTypes.Vulnerability{
Title: "PHPMailer 6.1.8 through 6.4.0 allows object injection through Phar Deserialization via addAttachment with a UNC pathname.",
Severity: "CRITICAL",
},
},
{
VulnerabilityID: "CVE-2018-19296",
PkgName: "wordpress",
InstalledVersion: wpVersion,
FixedVersion: "5.7.2",
Vulnerability: dbTypes.Vulnerability{
Title: "PHPMailer before 5.2.27 and 6.x before 6.0.6 is vulnerable to an object injection attack.",
Severity: "HIGH",
},
},
},
})
}
上記のように、独自のスキャン結果を追加すると、最終結果では以下のように表示されます。
┌───────────┬────────────────┬──────────┬─────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├───────────┼────────────────┼──────────┼─────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤
│ wordpress │ CVE-2020-36326 │ CRITICAL │ unknown │ 5.7.1 │ 5.7.2 │ PHPMailer 6.1.8 through 6.4.0 allows object injection │
│ │ │ │ │ │ │ through Phar Deserialization via addAttachment... │
│ ├────────────────┼──────────┤ │ │ ├────────────────────────────────────────────────────────────┤
│ │ CVE-2018-19296 │ HIGH │ │ │ │ PHPMailer before 5.2.27 and 6.x before 6.0.6 is vulnerable │
│ │ │ │ │ │ │ to an object... │
└───────────┴────────────────┴──────────┴─────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘
このようにTrivy本体のスキャン結果と同様に、独自ルールで定義したスキャナーを拡張機能として用意できました。
実践: PostScanner
でスキャン結果を更新してみる
こちらイメージを掴んでもらうために、独自にPostScanner
のみ実装したプラグインを用意しました。
プラグインのみでよかったのですが、よりそれらしいGoプロジェクトとDockerfileを含んでいます。
用意したプラグイン自体の処理は簡潔で、CVE-2023-25652
の脆弱性のスキャン結果に対して、Severity(重要度)と、タイトルの変更をしているだけの処理を行うものです。
まずは、プラグイン適用前のスキャン結果です。
対象のCVEのSeverityがHIGH
であることを確認できます。
┌───────────────────────┬────────────────┬──────────┬──────────────┬───────────────────┬──────────────────┬──────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├───────────────────────┼────────────────┼──────────┼──────────────┼───────────────────┼──────────────────┼──────────────────────────────────────────────────────────────┤
│ git │ CVE-2023-25652 │ HIGH │ affected │ 1:2.39.2-1.1 │ │ git: by feeding specially crafted input to `git apply │
│ │ │ │ │ │ │ --reject`, a path... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-25652 │
├───────────────────────┼────────────────┤ │ │ ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ git-man │ CVE-2023-25652 │ │ │ │ │ git: by feeding specially crafted input to `git apply │
│ │ │ │ │ │ │ --reject`, a path... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-25652 │
└───────────────────────┴────────────────┴──────────┴──────────────┴───────────────────┴──────────────────┴──────────────────────────────────────────────────────────────┘
ソースコードはこちら
type Plugin struct{}
func (p Plugin) Version() int {
return 1
}
func (p Plugin) Name() string {
return "plugin"
}
func (Plugin) PostScanSpec() serialize.PostScanSpec {
return serialize.PostScanSpec{
Action: api.ActionUpdate, // Update severity
IDs: []string{"CVE-2023-25652"},
}
}
func (Plugin) PostScan(results serialize.Results) (serialize.Results, error) {
for i, result := range results {
for j, vuln := range result.Vulnerabilities {
if vuln.VulnerabilityID != "CVE-2023-25652" {
continue
}
results[i].Vulnerabilities[j].Severity = "CRITICAL"
results[i].Vulnerabilities[j].Title = fmt.Sprintf("[ALERT] please alignment to security-team. detail: %s", results[i].Vulnerabilities[j].Title)
}
}
return results, nil
}
このプラグインをTinyGoでwasmファイルにコンパイルします。
tinygo build -o plugin.wasm -scheduler=none -target=wasi --no-debug plugin.go
作成されたwasmファイルをTrivyのModulesディレクトリにコピーします。
Trivy起動時に、このディレクトリ配下にあるwasmファイルを読み込みます。
cp plugin.wasm ~/.trivy/modules
コンテナイメージをスキャンします。
trivy image advent-calendar-2023-go
以下の結果から、Modulesが読み込まれていることに加えて、対象にしたCVEのSeverityがCRITICAL
に更新されていることを確認できます。
タイトルも独自の文言を追加できていることがわかります。
2023-12-25T00:00:00.000+0900 INFO Reading plugin.wasm...
2023-12-25T00:00:00.000+0900 INFO plugin.wasm loaded
2023-12-25T00:00:00.000+0900 INFO Registering WASM module: plugin@v1
2023-12-25T00:00:00.000+0900 INFO Vulnerability scanning is enabled
┌───────────────────────┬────────────────┬──────────┬──────────────┬───────────────────┬──────────────────┬──────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├───────────────────────┼────────────────┼──────────┼──────────────┼───────────────────┼──────────────────┼──────────────────────────────────────────────────────────────┤
│ git │ CVE-2023-25652 │ CRITICAL │ affected │ 1:2.39.2-1.1 │ │ [ALERT] please alignment to security-team. detail: git: by │
│ │ │ │ │ │ │ feeding specially crafted input... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-25652 │
├───────────────────────┼────────────────┼──────────┤ │ ├──────────────────┼──────────────────────────────────────────────────────────────┤
│ git-man │ CVE-2023-25652 │ CRITICAL │ │ │ │ [ALERT] please alignment to security-team. detail: git: by │
│ │ │ │ │ │ │ feeding specially crafted input... │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2023-25652 │
└───────────────────────┴────────────────┴──────────┴──────────────┴───────────────────┴──────────────────┴──────────────────────────────────────────────────────────────┘
まとめ
TrivyのModulesについて大きく2つの機能があることを紹介しました。
Trivy本体のバイナリに手を入れることなく、Trivy本体のスキャン結果に独自ルールを追加できるので、以下のような目的でも使えると思いました。
- CI で実行しているlinterや、IaCの設定ミスの検知をTrivyのModulesに移行する。
- 指定したCVEを検知した場合は、速やかに特定のチームへエスカレーションしてもらうために、Severityとタイトルを社内向けに更新するModulesを提供する
- 特定のバージョンにアップグレードしているライブラリを、CIで検知するModulesを提供する
個人的には、WebAssemblyで拡張できるようなOSSがこれからどんどん出てくるのでは、という未来を感じる年の瀬でした。