無料で脆弱性検査!Dockerfileに4行追加で導入できるmicroscannerを試してみた
先日レポートした「Docker漬けの一日を共に〜Docker Meetup Tokyo #23」は、情報量がてんこ盛りで、学び多くて楽しくてワッセロイだったんですが、その中で、とく(@CS_Toku)さんがLT発表されていた「KubeCon報告とmicroscanner試してみた」のmicroscannerが、面白そうだったので早速触ってみました。
Dockerfileに4行追加するだけで、CVEベースの脆弱性検査が無料で利用でき、既存のイメージビルドに組むこむのもお手軽そうなので、これからコンテナ導入しようと思っている人も、既に本番でガンガンコンテナ使っている人も、一度導入を検討してみてはいかがでしょうか。
__ (祭) ∧ ∧ Y ( ゚Д゚) Φ[_ソ__y_l〉 Docker ワッショイ |_|_| し'´J
MicroScannerとは?
aquasecurity/microscanner: Scan your container images for package vulnerabilities with Aqua Security
Aqua社が提供している、コンテナイメージの脆弱性スキャンツールです。
コンテナイメージに脆弱性が含まれていた場合、コンテナイメージのビルドを停止することができ、CI/CDへの導入も容易です。
エディションは3種類。
フリー版以外にも有料版やエンタープライズ版も用意されており、既存CI/CDパイプラインをフックしたり、コンテナイメージ内の悪意のあるファイルを検知する機能もあるとのこと。
今回は、検証ということでFree版を使ってみます。
MicroScannerを使ってみた
導入はハッキリ言って異常に簡単です。マジで。
トークンの登録
最初に、以下のコマンドで、microscanner利用に必要なトークンを登録します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | $ docker run -- rm -it aquasec /microscanner --register <your email address> Unable to find image 'aquasec/microscanner:latest' locally latest: Pulling from aquasec /microscanner 1160f4abea84: Pull complete aa607352b93a: Pull complete bf467aaa87ef: Pull complete Digest: sha256:2c3feb746740d3e76a0355cf0f45394a291cead41acb2b1906f62108aef85cf1 Status: Downloaded newer image for aquasec /microscanner :latest ___ ____ __ ____ ____ / _ |___ ___ _____ _/ __ /__ ____ / |/ (_)__________ / __ /______ ____ ___ ___ ____ / __ / _ `/ // / _ `/\ \/ -_) __/ / /|_/ / / __/ __/ _ \_\ \/ __/ _ `/ _ \/ _ \/ -_) __/ /_/ |_\_, /\_,_/\_,_ /___/ \__/\__/ /_/ /_/_/ \__ /_/ \___ /___/ \__/\_,_ /_//_/_//_/ \__ /_/ /_/ Aqua Security MicroScanner, version 2.6.4 Community Edition By proceeding, you are accepting the microscanner terms & conditions available at https: //microscanner .aquasec.com /terms . Accept and proceed? Y /N : y Please check your email for the token. |
利用規約に同意後、最初に指定したメールアドレスに、利用するトークンとMicroscannerの導入方法が送られてくるので、チェックしておきます。
Dockerfileへの追記
トークンを取得したら、以下の行を既存Dockerfileの後ろに追加します。
1 2 3 |
HTTPS接続のためのca-certificates導入
イメージにca-certificatesが含まれていない場合は、HTTPSコネクションをはるためのca-certificatesを導入しておく必要があります。Dockerfile内に以下のコマンド(例:Debian)で、導入しておきましょう。
1 | RUN apt-get update && apt-get -y install ca-certificates |
Dockerfileの例
実際に、Dockerfileを用意して実行してみます。以下のDockerfileを用意。
1 2 3 4 5 6 7 | FROM debian:jessie-slim RUN apt-get update && apt-get -y install ca-certificates RUN chmod +x /microscanner ARG token RUN /microscanner ${token} RUN echo "No vulnerabilities!" |
上の例では、microscanner利用のためのトークンは引数で与える仕様としています。
トークンの登録時に取得したトークンを利用して、Dockerfileをビルドします。
1 | $ docker build --build-arg=token=<TOKEN> --no-cache . |
脆弱性検査結果は、このようにJSONで出力されます。
1 2 3 | "vulnerability_summary" : { "negligible" : 13 }, |
これだと、脆弱性については、無視できるものが13個ということですね。特に問題ないとのこと。
microscannerの削除方法
イメージにmicroscannerを残しておきたくない場合は、以下をDockerfileに追加しておきましょう。
1 | RUN /microscanner ${token} && rm /microscanner |
脆弱性の出力結果サンプル
というわけで、どんなイメージを使ったら脆弱性が出力されるか、2つほど試してみました。
Apache Struts2の脆弱性を含むイメージ(CVE-2017-5638)
リモートで任意のコードを実行できる脆弱性CVE-2017-5638を含むイメージです。
Dockerリポジトリはこちら。
piesecurity/apache-struts2-cve-2017-5638 - Docker Hub
Dockerfileをこんな感じで作ります(いうても、冒頭のFROMを変えただけやで)。
1 2 3 4 5 6 7 8 | FROM piesecurity/apache-struts2-cve-2017-5638 RUN apt-get update && apt-get -y install ca-certificates RUN chmod +x /microscanner ARG token RUN /microscanner ${token} RUN echo "No vulnerabilities!" |
すると、サマリーがこんな感じで出力されます。
1 2 3 4 5 6 7 8 9 10 | "vulnerability_summary" : { "total" : 9, "high" : 3, "medium" : 5, "negligible" : 30, "score_average" : 6.188889, "max_score" : 9.3, "max_fixable_score" : 9.3, "max_fixable_severity" : "high" }, |
重要度がhighとmediumの脆弱性がそれぞれ3件と5件検出されています。量が多いのでここでは抜粋ですが、こんな感じで各パッケージについてのスキャン結果も合わせて出力されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | { "resource" : { "format" : "deb" , "name" : "wget" , "version" : "1.16-1+deb8u2" , "arch" : "amd64" , "cpe" : "pkg:/debian:8:wget:1.16-1+deb8u2" , "name_hash" : "af9d83836ecf1f49c598bcb1995b3c98" }, "scanned" : true , "vulnerabilities" : [ { "name" : "CVE-2016-7098" , "description" : "Race condition in wget 1.17 and earlier, when used in recursive or mirroring mode to download a single file, might allow remote servers to bypass intended access list restrictions by keeping an HTTP connection open." , "nvd_score" : 6.8, "nvd_score_version" : "CVSS v2" , "nvd_vectors" : "AV:N/AC:M/Au:N/C:P/I:P/A:P" , "nvd_severity" : "medium" , "vendor_score_version" : "Aqua" , "vendor_severity" : "negligible" , "vendor_statement" : "Minor issue\nhttp://git.savannah.gnu.org/cgit/wget.git/commit/?id=9ffb64ba6a8121909b01e984deddce8d096c498d\nhttp://git.savannah.gnu.org/cgit/wget.git/commit/?id=690c47e3b18c099843cdf557a0425d701fca4957" , "publish_date" : "2016-09-26" , "modification_date" : "2017-09-02" , "fix_version" : "1.18-4" , "solution" : "Upgrade operating system to debian version 9 (includes fixed version wget 1.18-4)" , "classification" : "The operating system vendor has classified the issue as a bug rather than a security issue, therefore the vulnerability has been classified as having negligible severity" }, { "name" : "DSA-4008-1" , "nvd_score" : 9.3, "nvd_score_version" : "CVSS v2" , "nvd_vectors" : "AV:N/AC:M/Au:N/C:C/I:C/A:C" , "nvd_severity" : "high" , "vendor_score" : 9.3, "vendor_score_version" : "CVSS v2" , "vendor_vectors" : "AV:N/AC:M/Au:N/C:C/I:C/A:C" , "vendor_severity" : "high" , "fix_version" : "1.16-1+deb8u4" , "solution" : "Upgrade package wget to version 1.16-1+deb8u4 or above." , "ref_vulns" : [ { "name" : "CVE-2017-13090" , "description" : "The retr.c:fd_read_body() function is called when processing OK responses. When the response is sent chunked in wget before 1.19.2, the chunk parser uses strtol() to read each chunk's length, but doesn't check that the chunk length is a non-negative number. The code then tries to read the chunk in pieces of 8192 bytes by using the MIN() macro, but ends up passing the negative chunk length to retr.c:fd_read(). As fd_read() takes an int argument, the high 32 bits of the chunk length are discarded, leaving fd_read() with a completely attacker controlled length argument. The attacker can corrupt malloc metadata after the allocated buffer." , "nvd_score" : 9.3, "nvd_score_version" : "CVSS v2" , "nvd_vectors" : "AV:N/AC:M/Au:N/C:C/I:C/A:C" , "nvd_severity" : "high" , "vendor_score" : 9.3, "vendor_score_version" : "CVSS v2" , "vendor_vectors" : "AV:N/AC:M/Au:N/C:C/I:C/A:C" , "vendor_severity" : "high" , "vendor_statement" : "http://git.savannah.gnu.org/cgit/wget.git/commit/?id=ba6b44f6745b14dce414761a8e4b35d31b176bba" , "publish_date" : "2017-10-27" , "modification_date" : "2017-12-29" , "fix_version" : "1.16-1+deb8u4" , "solution" : "Upgrade package wget to version 1.16-1+deb8u4 or above." }, { "name" : "CVE-2017-13089" , "description" : "The http.c:skip_short_body() function is called in some circumstances, such as when processing redirects. When the response is sent chunked in wget before 1.19.2, the chunk parser uses strtol() to read each chunk's length, but doesn't check that the chunk length is a non-negative number. The code then tries to skip the chunk in pieces of 512 bytes by using the MIN() macro, but ends up passing the negative chunk length to connect.c:fd_read(). As fd_read() takes an int argument, the high 32 bits of the chunk length are discarded, leaving fd_read() with a completely attacker controlled length argument." , "nvd_score" : 9.3, "nvd_score_version" : "CVSS v2" , "nvd_vectors" : "AV:N/AC:M/Au:N/C:C/I:C/A:C" , "nvd_severity" : "high" , "vendor_score" : 9.3, "vendor_score_version" : "CVSS v2" , "vendor_vectors" : "AV:N/AC:M/Au:N/C:C/I:C/A:C" , "vendor_severity" : "high" , "vendor_statement" : "http://git.savannah.gnu.org/cgit/wget.git/commit/?id=d892291fb8ace4c3b734ea5125770989c215df3f" , "publish_date" : "2017-10-27" , "modification_date" : "2017-12-29" , "fix_version" : "1.16-1+deb8u4" , "solution" : "Upgrade package wget to version 1.16-1+deb8u4 or above." } ] } |
AmazonLinux
2年前のAmazon Linuxで試してみました。Dockerリポジトリはこちら。
library/amazonlinux - Docker Hub
1 2 3 4 5 6 7 8 | FROM amazonlinux:2016.09.0.20161028 RUN yum install ca-certificates -y RUN chmod +x /microscanner ARG token RUN /microscanner ${token} RUN echo "No vulnerabilities!" |
すると、全てのパッケージに対して、以下のエラーが出力されてました。
1 | "scan_error": "unknown/unsupported operating system" |
microscannerの対象外OSということですね。現在の対象OSは以下の通り(2018/05/18現在)。
Supported operating system packages
- Debian >= 7, unstable
- Ubuntu LTS releases >= 12.04
- Red Hat Enterprise Linux >= 5
- CentOS >= 5
- Alpine >= 3.3
- Oracle Linux >= 5
microscanner利用上のベストプラクティス
公式サイトには、利用におけるベストプラクティスも掲載されていたので、合わせて紹介します。
トークンは秘匿する
microscanner利用時のトークンの値は秘密にしておきましょう。イメージビルド時は、Dockerfileにハードコーディングせずに引数として与えるのが良いです。
microscannerの実行タイミングに配慮する
microscannerを実行するタイミングは、Dockerfile内で全てのファイルやディレクトリ、パッケージがインストールされた後に実行するように設定しましょう。順番が逆になっている場合は、適切に脆弱性検査ができません。
docker build時には--no-cacheオプションを必ずづける
イメージ内容が変更されていない場合でも、新しい脆弱性を検出するためには、--no-cacheオプションが必須です。--no-cacheオプションを付与することで、イメージビルド時に必ずmicroscannerを実行できます。もちろん、この場合Dockerfileのすべてのステップの再実行が必要となりビルドが遅くなる可能性があるため、下のリンクを参考にし、microscannerが常に実行されるようにしてください。
プロダクション環境Dockerfileへの導入に注意する
プロダクション環境で利用しているイメージに、microscannerを導入したくない事も多いと思います。その場合は、脆弱性検査専用のDockerfileを別途用意しスキャン対象のイメージにmicroscannerを追加して実行することで、プロダクション環境のイメージに影響を与えません。
microscannerの出力に応じて、ビルドスペックを止めたりアラートをあげることができます。
まとめ「コンテナ環境へのDevSecOps導入にお手軽簡単有用便利」
今回のmicroscannerによるDockerイメージの脆弱性検査ですが、導入はかなり手頃な印象を受けました。利用するトークンを取得すれば、基本的にはDockerfileに4行追加するのみです。
「こんな検査1回だけやっておけばええやん、ビルドごとにやる意味あるの?」という気持ちになる方もいるかと思いますが、以下のシチュエーションでは、非常に有用かと思います。
- 機能拡張で新しいパッケージをイメージに導入したときの検査
- 既に本番運用している既存イメージのパッケージに新たに脆弱性が発見された時の初動対応
特に2つ目、プロダクション環境で運用しているイメージ内のパッケージに、新たに脆弱性が発見されることも十分にありえるでしょう。そう考えると、ビルドタイミングだけではなく定期的に日次でイメージの脆弱性検査を実施するのも良いんじゃないでしょうか。
DevSecOpsの文脈では、なるべく手をかけずにセキュリティ面含めてアプリケーションの品質を可能な限り向上させることが求められます。既存のビルドプロセスへの導入も比較的簡単に導入できそうなサービスなので、日頃のビルドプロセスへの組み込みを検討されてみてはいかがでしょうか。
それでは、今日はこのへんで。濱田(@hamako9999)でした。