Edited at

vulsを使って脆弱性の自動スキャンを実現する

More than 3 years have passed since last update.

改訂版記事を こちら にアップしています!

++++++

最近、脆弱性発見が相次いでいて、嬉しいようなツラいような状態ですね。

vulsは脆弱性確認を楽にしてくれる可能性を秘めた、サーバー運用者にとって期待のツールです。

導入手順は基本的にオフィシャルに従えば良いのですが、AWSベースで書かれているので、オンプレ用に端折った手順をメモしておきます。

以下、環境はCentOS7で、2016/06/01に入れた際の手順です。


事前準備

事前に必要なパッケージをインストールします。

goはtarでダウンロードするので、ちょっとめんどくさいですね。

$ yum -y install sqlite git gcc

$ wget https://storage.googleapis.com/golang/go1.6.2.linux-amd64.tar.gz
$ tar -C /usr/local -xzf go1.6.linux-amd64.tar.gz

/etc/profile.d/goenv.shを作成してこのように記載します。


/etc/profile.d/goenv.sh

export GOROOT=/usr/local/go

export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin


導入手順


cve-dictionaryサーバーの構築


ユーザーの用意

変な権限でサーバーが動くのは嫌なので、cve-dictionaryサーバー用ユーザー(vuls)を作ります。

UIDとか指定する場合は、よしなに。

$ useradd -s "/sbin/nologin" vuls


go-cve-dictionaryインストール

go-cve-dictionaryのダウンロードしましょう。結構重たいです。

$ go get github.com/kotakanbe/go-cve-dictionary

ところが2016/06/01のインストール時、ここでエラーが発生しました。

$ go get github.com/kotakanbe/go-cve-dictionary

# github.com/kotakanbe/go-cve-dictionary/server
go/src/github.com/kotakanbe/go-cve-dictionary/server/server.go:49: cannot use standard.New(bindURL) (type *standard.Server) as type engine.Server in argument to e.Run:
*standard.Server does not implement engine.Server (wrong type for SetLogger method)
have SetLogger(*"github.com/labstack/gommon/log".Logger)
want SetLogger("github.com/labstack/echo/log".Logger)

使っているライブラリの更新が災いしているようです。

暫定対処ですが、問題のライブラリを前のバージョンに戻します。

$ pushd go/src/github.com/labstack/echo

$ git log

commit a98843b6e58dcda9364099cc43c4d5b888baf00c
Author: Vishal Rana <vr@labstack.com>
Date: Tue May 31 18:29:11 2016 -0700
Logger as interface, fixed #533, fixed #349, fixed #304
Signed-off-by: Vishal Rana <vr@labstack.com>

commit 3e04718bf42e434bd027067272afc1947ddf2f8e
Merge: f73681b 9225ce0
Author: Vishal Rana <vr@labstack.com>
Date: Tue May 31 07:49:48 2016 -0700
Merge pull request #532 from 0x616E676572/add_cors_method
Add PATCH to default allowed methods

### Logger周りを変える前に戻す
$ git reset 3e04718bf42e434bd027067272afc1947ddf2f8e --hard

$ popd
$ go get github.com/kotakanbe/go-cve-dictionary

強引ですが、go-cve-dictionaryがインストールされました。


daemon化の準備

ビルドされたgo-cve-dictionaryをOSのPATHにコピー。

※ こうしないとrootユーザーのホームにバイナリがあり、vulsユーザーが実行できないため。

$ cp -p go/bin/go-cve-dictionary /usr/local/sbin/

ログディレクトリおよび辞書ディレクトリを作って、vulsユーザーに権限を渡しておきます。

$ mkdir /var/log/vuls

$ chown vuls:vuls /var/log/vuls

$ mkdir -p /var/vuls/dict
$ chown vuls:vuls /var/vuls/dict


脆弱性辞書の準備

2002年〜2016年に発生した脆弱性について、辞書を取得します。

数分は時間が掛かるので注意。

$ for i in {2002..2016}; do go-cve-dictionary fetchnvd -years $i; done

### sqliteファイルができたことを確認
$ ls -l cve.sqlite3

権限を変えて、辞書ディレクトリにsqlite3ファイルを移動します。

$ chown vuls:vuls cve.sqlite3

$ mv cve.sqlite3 /var/vuls/dict

$ ls -l /var/vuls/dict/cve.sqlite3
-rw-r--r-- 1 vuls vuls 574898176 Jun 1 12:00 cve.sqlite3


起動スクリプトの準備

起動スクリプト(systemdファイル)を作成します。

### 環境設定ファイル作成

$ vim /etc/sysconfig/go-cve-dictionary

BIND=0.0.0.0
PORT=1323
DBPATH=/var/vuls/dict/cve.sqlite3

### 起動スクリプト作成
$ vim /usr/lib/systemd/system/go-cve-dictionary.service
### 下記参照

go-cve-dictionary.serviceの中身はこんな感じです。

httpdを参考にしているので、ちょっと過剰かもしれません。


go-cve-dictionary.service

[Unit]

Description=Go CVE Dictionary Server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=simple
EnvironmentFile=/etc/sysconfig/go-cve-dictionary
ExecStart=/usr/local/sbin/go-cve-dictionary server -bind=${BIND} -port=${PORT} -dbpath=${DBPATH}
ExecStop=/bin/kill ${MAINPID}
User=vuls
Group=vuls
PrivateTmp=true

[Install]
WantedBy=multi-user.target



go-cve-dictinaryを起動

ようやく準備ができました。go-cve-dictinaryを起動しましょう。

$ systemctl daemon-reload

$ systemctl start go-cve-dictionary

### 確認
$ systemctl start go-cve-dictionary

$ ps -ef | grep go-cve-dictionary

vuls 25721 1 0 12:27 ? 00:00:00 /usr/local/sbin/go-cve-dictionary server -bind=0.0.0.0 -port=1323 -dbpath=/var/vuls/dict/cve.sqlite3

$ netstat -alnp | grep go-cve | grep LISTEN

tcp6 0 0 :::1323 :::* LISTEN 25721/go-cve-dictio


vulsを準備

vulsを手に入れる・・・のですが、 ここで詰まる可能性がある ので、続く手順を先に眺めてください。

$ go get github.com/future-architect/vuls

CentOS6の標準yumリポジトリでは、git-1.X系列しかダウンロード出来ないはずです。

この場合、gitのバージョンが古いためにgo getが詰まってしまいます。

そこでwandiscoのリポジトリからgit-2.xをインストールします。


git-2.xのインストール

/etc/yum.repos.d/wandisco.repoを作成します。


/etc/yum.repos.d/wandisco.repo

[wandisco-git]

name=WANdisco Distribution of git
baseurl=http://opensource.wandisco.com/centos/7/git/$basearch
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-WANdisco

gitのアップデート

### GPGキーを取り込み

$ rpm --import http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco
$ yum update git --enablerepo=wandisco-git

### バージョンアップ確認
$ git --version
git version 2.8.0


改めてvulsをインストール

改めてvulsをgo getしましょう。

$ go get github.com/future-architect/vuls

入りました。

vulsもOSのパスにコピーしておきましょう。

$ cp -p go/bin/vuls /usr/local/bin/

確認

$ /usr/local/bin/vuls -v

vuls 0.1.4


vulsによる脆弱性検査


vulsの使用準備


vuls設定ファイルの作成

先ほど作ったvulsサーバーにてconfig.tomlファイルを作成します。

現在のvulsはカレントディレクトリ以下にあるconfig.tomlファイルを読み、カレントディレクトリ以下に結果を出力するので、 検査のたびに適当なフォルダを作って、その中で作業するのが良さそうです。

$ mkdir hoge

$ cd hoge
$ vim config.toml

中身はこのように書いていきます。

詳しくはオフィシャルを参照してください。


config.toml

[servers]

[servers.localhost]
host = "127.0.0.1"
port = "22"
user = "root"
keyPath = "/root/.ssh/id_rsa"



SSH公開鍵認証の設定

先ほどの設定ファイルで指定していることから想像できると思いますが、vulsはSSHの公開鍵認証を使用しているようです。検査対象サーバー(ここではローカルホスト)には事前にSSH周りの設定をしておきましょう。

なお、このようにSSHをしたりする関係で、 vulsユーザー(daemon用ユーザー)とvuls実行ユーザー(スキャン実行用ユーザー)は分けておいたほうが無難な気がします。

このあたり、AnsibleやChefなどのSSHユーザー管理の話と同じですので、ここでは割愛します。

私の環境ではAnsibleに使用しているユーザーを使用することにしました。

なお、以下はSSH公開鍵設定の手順例です。

### 認証用の公開鍵を作る

[[vulsサーバー]]
$ su - vuls実行ユーザー

[vuls実行ユーザー] $ ssh-keygen -t rsa
[vuls実行ユーザー] $ cat ~/.ssh/id_rsa.pub

### この内容を検査対象サーバーのSSHログインユーザーに設定
[[検査対象サーバー]]
[vuls実行ユーザー] $ vim ~/.ssh/authorized_keys
[vuls実行ユーザー] $ chmod 600 ~/.ssh/authorized_keys

SSHで対象サーバーに入れることを確認しましょう。


脆弱性スキャンの実行


vuls prepare

SSH公開鍵ログイン設定が終わったらvuls prepareを実行します。

これにより、SSHの動作確認ができるとともに、動作に必要なyumプラグイン

が検査対象サーバーにインストールされます。

vuls prepare

INFO[0000] Start Preparing (config: /root/config.toml)
[May 24 12:29:25] INFO [localhost] Detecting OS...
[May 24 12:29:25] INFO [localhost] (1/1) Detected localhost: centos 6.4
[May 24 12:29:25] INFO [localhost] Detecting Container OS...
[May 24 12:29:25] INFO [localhost] Installing...
[May 24 12:29:25] INFO [localhost] Ignored: yum-plugin-security already installed
[May 24 12:29:25] INFO [localhost] Installing yum-plugin-changelog...
[May 24 12:29:29] INFO [localhost] Installed: yum-plugin-changelog
[May 24 12:29:29] INFO [localhost] Success


vuls scan

ここまで準備出来れば、脆弱性スキャンが実行できます。

config.tomlに書いたサーバーに対しては一気に実行してしまいます ので、稼働中のホストへのスキャンは気をつけてください。

レポートの出力形式はヘルプを見て、適当なものを選びましょう。

$ cd vulsの設定ファイルを作ったフォルダ

$ vuls scan -report-text

結果はカレントディレクトリに出力されています。

$ ls -l results/current


定期的な実行を考える

定期的な実行をし続けることで、運用をもっと楽したいと思います。


脆弱性情報の定期的な更新

とりあえずcronに仕込んじゃいましょう。

コマンドの中身は簡単なワンライナーで、「過去2年間の最新情報を取得する」というものです。


/etc/cron.d/go-cve-dictionary_update

SHELL=/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
MAILTO=""
HOME=/home/vuls

# Update CVE dict
37 05 * * * vuls go-cve-dictionary fetchnvd -last2y -dbpath=/var/vuls/dict/cve.sqlite3 >/dev/null 2>&1


実行ユーザーは環境に合わせてください。

また、実行時間は 負荷をかけないように適当に変えてください。


定期的な脆弱性診断

この設定は、どうやって通知するのが適切か考える必要があります。

vulsはslackに投げる機能もあるのですが、脆弱性発見時は結構な文章量になることを想定して、メール通知で始めてみることにしました。


メール通知を設定する

Cronで実行するためのconfigファイルを作成します。

$ mkdir /var/vuls/cron/config.toml

$ vim /var/vuls/cron/config.toml

自動実行して負荷をかけても問題ないサーバーをスキャン対象にしておきましょう。

このとき、configファイルの先頭に[mail]ブロックを作成します。

詳しくはオフィシャルの設定を見るとして、こんな感じでローカルのsmtpサーバーを使って送信してしまいます。


/var/vuls/cron/config.toml

[mail]

smtpAddr = "localhost"
smtpPort = "25"
from = "xxxxxx@mailaddress.xxxx"
to = ["xxxxxx@mailaddress.xxxx"]
subjectPrefix = "[vuls]"

[servers]
### 以下スキャン対象のサーバーの指定


なお、オフィシャルではsmtpPortオプションはintegerに見えるのですが、文字列(ダブルクォートで囲む)じゃないとうまく動作しませんでした。バグ?


Cronに設定する

この設定ファイルを使ってvuls scanを実行する設定をCronに仕込みます。

vulsを実行するユーザー(対象ホストに公開鍵でSSHログインできるユーザー)のCronとして設定する必要があります。

私の場合、先ほどのconfigファイルはそのユーザーの/var/vuls/cronに置いているので、vulsコマンド実行前にcdしています。


/etc/cron.d/go-cve-dictionary_scan

SHELL=/bin/bash

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
MAILTO=""
HOME=vuls実行ユーザーのホームディレクトリ

# Scan target machines
37 06 * * * vuls実行ユーザー cd /var/vuls/cron && vuls scan -report-text -report-mail >/dev/null 2>&1


これで 毎朝嫌というほど脆弱性通知メールが来ることが期待できます。

# 当たり障りない脆弱性は対応を手抜きしちゃいますよね・・・


最後に

以上で、かなり強引ながら、脆弱性を自動スキャンしてメール通知する仕組みを構築しました。

作ってみてすぐに察しましたが、毎日脆弱性通知が来ると すぐに慣れて見なくなってしまう という懸念がありますね。このあたり、通知方法などをブラッシュアップしていかないといけない気がしています。

投稿日時を見るとわかりますが、ほとんどテストすることなく書いた記事なので間違っていたらすみません!

# 間違いに気付き次第修正します・・・