注目の記事

無料で脆弱性検査!Dockerfileに4行追加で導入できるmicroscannerを試してみた

microscannerは、CVEベースでDockerイメージの脆弱性検査をするツールです。簡単に導入できかつ有用なので、導入方法と利用上の注意事項などをまとめました。
2018.05.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

先日レポートした「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利用に必要なトークンを登録します。

$ 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の後ろに追加します。

ADD https://get.aquasec.com/microscanner /
RUN chmod +x /microscanner
RUN /microscanner <TOKEN>

HTTPS接続のためのca-certificates導入

イメージにca-certificatesが含まれていない場合は、HTTPSコネクションをはるためのca-certificatesを導入しておく必要があります。Dockerfile内に以下のコマンド(例:Debian)で、導入しておきましょう。

RUN apt-get update && apt-get -y install ca-certificates

Dockerfileの例

実際に、Dockerfileを用意して実行してみます。以下のDockerfileを用意。

Dockerfile

FROM debian:jessie-slim
RUN apt-get update && apt-get -y install ca-certificates
ADD https://get.aquasec.com/microscanner /
RUN chmod +x /microscanner
ARG token
RUN /microscanner ${token}
RUN echo "No vulnerabilities!"

上の例では、microscanner利用のためのトークンは引数で与える仕様としています。

トークンの登録時に取得したトークンを利用して、Dockerfileをビルドします。

$ docker build --build-arg=token=<TOKEN> --no-cache .

脆弱性検査結果は、このようにJSONで出力されます。

  "vulnerability_summary": {
    "negligible": 13
  },

これだと、脆弱性については、無視できるものが13個ということですね。特に問題ないとのこと。

microscannerの削除方法

イメージにmicroscannerを残しておきたくない場合は、以下をDockerfileに追加しておきましょう。

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を変えただけやで)。

Dockerfile

FROM piesecurity/apache-struts2-cve-2017-5638

RUN apt-get update && apt-get -y install ca-certificates
ADD https://get.aquasec.com/microscanner /
RUN chmod +x /microscanner
ARG token
RUN /microscanner ${token}
RUN echo "No vulnerabilities!"

すると、サマリーがこんな感じで出力されます。

  "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"
  },

重要度がhighmediumの脆弱性がそれぞれ3件と5件検出されています。量が多いのでここでは抜粋ですが、こんな感じで各パッケージについてのスキャン結果も合わせて出力されています。

    {
      "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",
          "nvd_url": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-7098",
          "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",
          "vendor_url": "https://security-tracker.debian.org/tracker/CVE-2016-7098",
          "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",
          "vendor_url": "https://security-tracker.debian.org/tracker/DSA-4008-1",
          "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",
              "nvd_url": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-13090",
              "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",
              "vendor_url": "https://security-tracker.debian.org/tracker/CVE-2017-13090",
              "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",
              "nvd_url": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-13089",
              "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",
              "vendor_url": "https://security-tracker.debian.org/tracker/CVE-2017-13089",
              "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

Dockerfile

FROM amazonlinux:2016.09.0.20161028

RUN yum install ca-certificates -y
ADD https://get.aquasec.com/microscanner /
RUN chmod +x /microscanner
ARG token
RUN /microscanner ${token}
RUN echo "No vulnerabilities!"

すると、全てのパッケージに対して、以下のエラーが出力されてました。

      "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が常に実行されるようにしてください。

New feature request: Selectively disable caching for specific RUN commands in Dockerfile · Issue #1996 · moby/moby

プロダクション環境Dockerfileへの導入に注意する

プロダクション環境で利用しているイメージに、microscannerを導入したくない事も多いと思います。その場合は、脆弱性検査専用のDockerfileを別途用意しスキャン対象のイメージにmicroscannerを追加して実行することで、プロダクション環境のイメージに影響を与えません。

microscannerの出力に応じて、ビルドスペックを止めたりアラートをあげることができます。

まとめ「コンテナ環境へのDevSecOps導入にお手軽簡単有用便利」

今回のmicroscannerによるDockerイメージの脆弱性検査ですが、導入はかなり手頃な印象を受けました。利用するトークンを取得すれば、基本的にはDockerfileに4行追加するのみです。

「こんな検査1回だけやっておけばええやん、ビルドごとにやる意味あるの?」という気持ちになる方もいるかと思いますが、以下のシチュエーションでは、非常に有用かと思います。

  • 機能拡張で新しいパッケージをイメージに導入したときの検査
  • 既に本番運用している既存イメージのパッケージに新たに脆弱性が発見された時の初動対応

特に2つ目、プロダクション環境で運用しているイメージ内のパッケージに、新たに脆弱性が発見されることも十分にありえるでしょう。そう考えると、ビルドタイミングだけではなく定期的に日次でイメージの脆弱性検査を実施するのも良いんじゃないでしょうか。

DevSecOpsの文脈では、なるべく手をかけずにセキュリティ面含めてアプリケーションの品質を可能な限り向上させることが求められます。既存のビルドプロセスへの導入も比較的簡単に導入できそうなサービスなので、日頃のビルドプロセスへの組み込みを検討されてみてはいかがでしょうか。

それでは、今日はこのへんで。濱田(@hamako9999)でした。