Vulsのコードを読む その3 Vuls scanを調べてみた (Vulsへのプルリク)
Vulsのコードを読む その1 全体像の把握, Vulsのコードを読む その2 go-cve-dictionary を理解する、バグを見つけてプルリクを出してマージされるまでの続きになります。
今回は、Vulsのscan部分を具体的に掘り下げて理解していきたいと思います。
- vuls scan
- non-packages scanの例
- L121 NewCustomLogger() logフォルダの作成
- L123 mkdirDotVuls() homeディレクトリに.vulsフォルダの作成
- L130~L136 SSH key 入力
- L138~L147 設定ファイル(config.toml)の読み込み
- L162~L191 servernamesの設定
- L198 ValidateOnScan() 設定ファイルの検証
- L203 InitServers() スキャン対象の特定
- L209 CheckScanModes() スキャンできるかどうかの確認
- L215 DetectPlatforms() プラットフォーム(AWSなのかContainerなのか)の確認
- L217 DetectIPSs() Trend MicroのDeep Securityの設定確認
- L220 Scan() パッケージ情報などを取得し保存
- 感想
vuls scan
non-packages scanの例
Scan vulnerabilites of non-OS packages · Vuls
ssh接続せずにCPEで指定したソフトウェアの脆弱性を検出する機能で簡単にvuls scanからvuls reportを実行した場合を説明します。
公式ドキュメントに従い、config.tomlを以下のように設定します。
vuls scanを実行します。
次にvuls report -format-full-textでスキャン結果を表示します。
このように、config.tomlで設定されたCPE(cpe:/o:fortinet:fortios:4.3.0)をスキャンし、脆弱性を確認することができます。
今回は上記の中でもvuls scanの部分を説明します。
対象となるソースコードはvuls/commands/scan.go になります。
前半の部分は以前に説明していますので、L123のExecute() 以下の部分から説明していきます。
L121 NewCustomLogger() logフォルダの作成
Logフォルダの生成などを行なっています。
/var/log/vuls にlogが置かれるようになっています。
logの生成にはlogursというライブラリが使用されています。
L123 mkdirDotVuls() homeディレクトリに.vulsフォルダの作成
homeディレクトリに.vulsフォルダを作成しています。
.vulsフォルダの中には何も入っていなかったため今回のケースでは特に利用されないようです。
(scan/executil.goのcontrolPathとして.vulsが利用されていますがcontrolPathが他で利用されていないため現在は利用されていないかもしれません。)
homeディレクトリを取得する部分にはgo-homedirというライブラリが使用されています。
homeディレクトリを取得するために os/user という標準パッケージがありますが、そちらを使うとクロスコンパイルができないという問題があるそうです。
L130~L136 SSH key 入力
今回は利用していませんが、ask-key-passwordフラグを立てた場合にはSSH key入力ができるようになります。
commands/util.goの getPasswd() でgopassというライブラリを使い、ターミナル上から入力を受け付け、パスワードのマスク処理を実現しています。
L138~L147 設定ファイル(config.toml)の読み込み
設定ファイルのconfig.tomlファイルの読み込み処理を行なっています。
config/tomlloader.goのLoad()でtomlファイルの読み込み、エラー処理等を行なっています。
このように読み込み設定だけでも量が多く、それぞれの設定のエラー処理、設定されていない場合のデフォルト設定などもされているためコード行数は結構多いです。
エラーがあった場合にはlog出力されます。
L162~L191 servernamesの設定
servernamesの取得、設定を行なっています。
servernamesはconfig.tomlで設定したサーバーの中からどのサーバーをscanするのかを設定するときに利用するものです。
vuls scan と引数なしで実行した場合にはconfig.tomlで設定されたサーバー(今回の場合だとservers.forti)へスキャンをかけます。
vuls scan forti と明記することで他のサーバーがconfig.tomlで設定されている場合にfortiのみにスキャンをかけるという設定ができます。
引数の取得は標準パッケージのflagのArgs()でフラグ以外の引数を取得できます。(L163)
引数がない場合にはioutil.ReadAll(os.Stdin)でパイプで渡された入力(servernames)を受け取る処理が行われます。(L165)
L198 ValidateOnScan() 設定ファイルの検証
config/config.goのValidateOnScan()で設定ファイルの検証を行います。
ssh keyのファイルパスが正しいかどうか、ResultsDirのファイルパスの設定が正しいかどうかなどを行なっています。
govalidatorというライブラリのIsFilePath()を使うことでファイルパスがWindowsのパスかどうか、Unixのパスかどうかの判定と正しい形式かどうかの確認を行うことができます。
また、VlidateOnScan() の部分にバグ?を発見しました。
L180~L185とL191~L196の部分が全く同じ処理を行なっています。
そのため、どちらか一つを実行すれば本来良いと思われるが2回エラーチェックが行われているように思われます。
実際に以下のように -results-dir に絶対パスではない形で入力してみると、2回同じエラーが出力されます。
2回行われているエラーチェック部分を一つだけに修正して再度実行させた結果、以下のように1回のみERROが表示されるようになりました。
小さなバグでもプルリク出してみる。
こちらも無事マージされました!
L203 InitServers() スキャン対象の特定
InitServers()でサーバーのスキャンなのか、コンテナのスキャンなのか、コンテナのイメージへのスキャンなのか、設定ファイルから特定します。
サーバーのスキャンの場合は、OSは何か(Debian, RedHat, SUSEなど)の特定も行います。
OSの特定はdetectOS()で行われていますが、具体的には
ls /etc/debian_version
ls /etc/fedora-release
といったコマンドを実行し、正しく取得できたかどうかを見ているようです。
L209 CheckScanModes() スキャンできるかどうかの確認
InitServers()で特定したサーバーがスキャンできるかどうかの確認を行なっているようです。
おそらく、設定ファイルが過去に作成されて現在の書き方に対応していない場合のエラーチェックだと思います。
L215 DetectPlatforms() プラットフォーム(AWSなのかContainerなのか)の確認
detectPlatform()の部分で、InitServers()で特定した情報を利用し、さらに詳細な情報、サーバーならAWSなのか、インスタンスIDは何か、確認していきます。
awsの場合、detectRunningOnAws() にて以下のコマンドを実行し、AWSのインスタンスIDを取得します。
curl --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id
169.254.169.254はローカルアドレスで、上記のURLはAWSのEC2のメタデータを取得するためにAWS側で定義されているもののようです。
インスタンスメタデータとユーザーデータ - Amazon Elastic Compute Cloud
L217 DetectIPSs() Trend MicroのDeep Securityの設定確認
こちらはTrend MicroのDeep Securityの情報を取得する部分となっているようです。
実際の処理はdetectDeepSecurity()で行われています。
この部分に関してはVulsの公式ドキュメントにあまり書かれていない部分のように思われます。
Vulsの有料版のFuture Vulsのドキュメントでは外部連携 DEEPSECURITYと書かれたページがあるので、こちらが参考になると思います。
L220 Scan() パッケージ情報などを取得し保存
カーネル情報、インストールされているパッケージ情報、アップデート可能なパッケージ情報などを取得して、resultsディレクトリ以下に結果の保存を行います。
この部分でメインとなる処理を行なっている部分はGetScanResults()で、状況の情報の取得や、Wordpresのバージョン、テーマ、プラグインの情報の取得、コンテナで使われているライブラリのパス、ライブラリ名、バージョン情報の取得などが行われています。
ライブラリ周りに関してはaquasecurityのfanal, go-dep-parser が利用されています。
go-dep-parserはnpmやcomposerなどの各言語毎のdependencyをパースするものです。
fanalはdockerの静的解析を行うもので、dockerで利用されているライブラリを特定することができます。また、ライブラリだけでなく、OSの特定やパッケージの特定なども行うことができます。
VulsのScan() の最後に writeScanResults() によってこれまで特定してきた情報をresultsディレクトリ以下にscan実行時間の名前のディレクトリを作成してjson形式で出力します。
これでvuls scanの処理が完了します。
そのため、vuls scanではscan対象がどのような状態なのかの確認のみを行い、その後のreportで脆弱性データベースとscan結果の情報と付き合わせてレポートを作成するようです。
感想
デバッガの利用
デバッガを使って処理を追う方がソースコードをただ読んでいくよりも効率よく理解が進むと思われますが、私があまり使いこなせていないために今回は利用しませんでした。
Visual Studio CodeでGo言語のデバッグ環境を整える - Qiita
使い方を少しずつ学んでいきたい。
Vulsで使われているライブラリ
Vulsの中で使われているライブラリも調べてみると新しい発見も多くありました。
Log
GitHub - sirupsen/logrus: Structured, pluggable logging for Go.
【Go×ログ】logrusの使い方を簡単に分かりやすくまとめてみた - Qiita
homeディレクトリ
パスワードのターミナル入力
GitHub - howeyc/gopass: getpasswd for Go
dockerの静的解析
GitHub - aquasecurity/fanal: Static Analysis Library for Containers
最後に
Goを始めるに当たってスターティングGo言語を読んで文法などの基礎を身につけましたがVulsのコードを読んでいると実際の開発でのテクニックの部分も学ぶ必要があると感じています。
まだ全部は読めていませんが改訂2版 みんなのGo言語が基礎を理解した人がさらにステップアップするために必要な情報がまとまった本のように思いますので、この本を読んでGo言語、Vulsの理解を深めていきたいです。
改訂2版 みんなのGo言語は2016年に刊行したものを2019年8月1日に全章アップデートされたものなので今読む本としてはおすすめです。
実際に読むと前提知識を知らないために躓く部分もあるとは思いますが、そこも調べながら読み進めると得られるものが多いと思います。
前回はgo-cve-dictionaryへのプルリクでしたが、今回はVuls本体へのプルリクを出して無事マージされました!
今回も本当に小さな修正ですが、少しずつ本当に価値のあるプルリクを投げれるようになれればいいのかなと思っています。
Github starが7.6kもあるOSSへ貢献できたというのはとても嬉しいです。
次回はreportの部分をより詳しく見ていきたいと思います。
また、IssueHuntを利用してVulsのIssueへプルリクを投げ、IssueHuntデビューもできたらしてみたい。
その1、その2、その4はこちら。
Vulsのコードを読む その1 全体像の把握 - Security Index
Vulsのコードを読む その2 go-cve-dictionary を理解する、バグを見つけてプルリクを出してマージされるまで - Security Index
Vulsのコードを読む その4 Vuls reportを調べてみた - Security Index
Twitter アカウント