どうも、若松です。

最近、コンテナイメージをビルドしまくっており、オレオレなコンテナイメージが増殖しています。
そんなおり、以下の記事が目に止まりました。

DockerHubで公開されているコンテナが安全か確かめてみた結果【人気のコンテナ上位800個】

そりゃあ脆弱性あるわなぁと思いつつ、じゃあオレのコンテナイメージは大丈夫か?と思いました。
というわけで上記の記事で使用されていたdockleTrivyを使って、オレオレコンテナイメージをチェックしていきたいと思います。

使用ツール

dockle

CISベンチマークやDockerベストプラクティスに準拠したコンテナイメージかをチェックできます。
WARNやFATALの項目には、どのように改善すればよいかのヒントが添えられていて小憎いです

それぞれの基準は以下の通り
CISベンチマーク
Dockerベストプラクティス

Trivy

OSやアプリケーションに潜む脆弱性をチェックできます。
CVE番号と共に脆弱性の詳細を表示してくれるので、どのように改善するかの計画が立てやすいです。

対象OSとアプリケーションは以下の通り
https://github.com/knqyf263/trivy#vulnerability-detection

インストール

今回はMacのローカルにコンテナイメージがあるため、brewでインストールしました。
手順はそれぞれのREADMEに記載があるため、省略します。

実行結果

前回の記事で作成したLaravelコンテナを対象にしました。
https://cloudpack.media/48301

dockle

$ dockle laravel:latest
WARN    - CIS-DI-0001: Create a user for the container
    * Last user should not be root
PASS    - CIS-DI-0005: Enable Content trust for Docker
WARN    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image
    * not found HEALTHCHECK statement
PASS    - CIS-DI-0007: Do not use update instructions alone in the Dockerfile
PASS    - CIS-DI-0008: Remove setuid and setgid permissions in the images
PASS    - CIS-DI-0009: Use COPY instead of ADD in Dockerfile
PASS    - CIS-DI-0010: Do not store secrets in ENVIRONMENT variables
PASS    - CIS-DI-0010: Do not store secret files
PASS    - DKL-DI-0001: Avoid sudo command
PASS    - DKL-DI-0002: Avoid sensitive directory mounting
PASS    - DKL-DI-0003: Avoid apt-get/apk/dist-upgrade
PASS    - DKL-DI-0004: Use apk add with --no-cache
PASS    - DKL-DI-0005: Clear apt-get caches
WARN    - DKL-DI-0006: Avoid latest tag
    * Avoid 'latest' tag
PASS    - DKL-LI-0001: Avoid empty password
PASS    - DKL-LI-0002: Be unique UID
PASS    - DKL-LI-0002: Be unique GROUP

大きな問題はありませんでしたが、WARNが3件出て、改善に向けたアドバイスが表示されていることがわかります。

Trivy

$ trivy laravel:latest
2019-07-16T00:26:44.376+0900    INFO    Updating vulnerability database...
2019-07-16T00:26:46.203+0900    WARN    You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed
2019-07-16T00:26:48.159+0900    INFO    Detecting Alpine vulnerabilities...
2019-07-16T00:26:48.171+0900    INFO    Updating composer Security DB...
2019-07-16T00:26:51.928+0900    INFO    Detecting composer vulnerabilities...
2019-07-16T00:26:51.928+0900    INFO    Updating composer Security DB...
2019-07-16T00:26:53.211+0900    INFO    Detecting composer vulnerabilities...
2019-07-16T00:26:53.214+0900    INFO    Updating composer Security DB...
2019-07-16T00:26:54.422+0900    INFO    Detecting composer vulnerabilities...
2019-07-16T00:26:54.422+0900    INFO    Updating composer Security DB...
2019-07-16T00:26:56.052+0900    INFO    Detecting composer vulnerabilities...

var/www/laravel/vendor/psy/psysh/vendor-bin/box/composer.lock
=============================================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)


var/www/laravel/vendor/hamcrest/hamcrest-php/composer.lock
==========================================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)


laravel:latest (alpine 3.10.1)
==============================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)


var/www/laravel/vendor/phar-io/manifest/composer.lock
=====================================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)


var/www/laravel/composer.lock
=============================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

脆弱性は検出されませんでしたが、AlpineLinuxに加えてComposerも対象に診断を行ってくれることがわかります。

引っかかる場合の表示

上記でチェックしたコンテナイメージは最新のベースイメージであったため、引っかかる項目が少ない結果となりました。
しかしながら、これではチェックに引っかかった場合の表示がわからないため、ずいぶん前(docker imagesで見ると9ヶ月前)にpullして手元にあったhttpd:2.4-alpineをチェックしてみます。

dockle

$ dockle httpd:2.4-alpine
WARN    - CIS-DI-0001: Create a user for the container
    * Last user should not be root
PASS    - CIS-DI-0005: Enable Content trust for Docker
WARN    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image
    * not found HEALTHCHECK statement
PASS    - CIS-DI-0007: Do not use update instructions alone in the Dockerfile
PASS    - CIS-DI-0008: Remove setuid and setgid permissions in the images
PASS    - CIS-DI-0009: Use COPY instead of ADD in Dockerfile
PASS    - CIS-DI-0010: Do not store secrets in ENVIRONMENT variables
PASS    - CIS-DI-0010: Do not store secret files
PASS    - DKL-DI-0001: Avoid sudo command
PASS    - DKL-DI-0002: Avoid sensitive directory mounting
PASS    - DKL-DI-0003: Avoid apt-get/apk/dist-upgrade
FATAL   - DKL-DI-0004: Use apk add with --no-cache
    * Use --no-cache option if use 'apk add': /bin/sh -c set -eux;  runDeps='       apr-dev         apr-util-dev        apr-util-ldap       perl    ';  apk add --no-cache --virtual .build-deps        $runDeps        ca-certificates     coreutils       dpkg-dev dpkg       gcc         gnupg   libc-dev        libressl        libressl-dev        libxml2-dev         lua-dev         make        nghttp2-dev         pcre-dev        tar         zlib-dev    ;       ddist() {       local f="$1"; shift;        local distFile="$1"; shift;         local success=;         local distUrl=;         for distUrl in $APACHE_DIST_URLS; do        if wget -O "$f" "$distUrl$distFile" && [ -s "$f" ]; then        success=1;              break;          fi;     done;       [ -n "$success" ];  };      ddist 'httpd.tar.bz2' "httpd/httpd-$HTTPD_VERSION.tar.bz2";     echo "$HTTPD_SHA256 *httpd.tar.bz2" | sha256sum -c -;       ddist 'httpd.tar.bz2.asc' "httpd/httpd-$HTTPD_VERSION.tar.bz2.asc";     export GNUPGHOME="$(mktemp -d)"; for key in         A93D62ECC3C8EA12DB220EC934EA76E6791485A8    B9E8213AEFB861AF35A41F2C995E35221AD84DFF    ; do        gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key";  done;   gpg --batch --verify httpd.tar.bz2.asc httpd.tar.bz2;   command -v gpgconf && gpgconf --kill all || :;  rm -rf "$GNUPGHOME" httpd.tar.bz2.asc;      mkdir -p src;   tar -xf httpd.tar.bz2 -C src --strip-components=1;  rm httpd.tar.bz2;   cd src;         patches() {         while [ "$#" -gt 0 ]; do            local patchFile="$1"; shift;            local patchSha256="$1"; shift;          ddist "$patchFile" "httpd/patches/apply_to_$HTTPD_VERSION/$patchFile";  echo "$patchSha256 *$patchFile" | sha256sum -c -;           patch -p0 < "$patchFile";           rm -f "$patchFile";     done;   };  patches $HTTPD_PATCHES;         gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)";  ./configure         --build="$gnuArch"      --prefix="$HTTPD_PREFIX"        --enable-mods-shared=reallyall      --enable-mpms-shared=all    ;   make -j "$(nproc)";     make install;       cd ..;  rm -r src man manual;       sed -ri         -e 's!^(\s*CustomLog)\s+\S+!\1 /proc/self/fd/1!g'       -e 's!^(\s*ErrorLog)\s+\S+!\1 /proc/self/fd/2!g'        "$HTTPD_PREFIX/conf/httpd.conf";    runDeps="$runDeps $(        scanelf --needed --nobanner --format '%n#p' --recursive /usr/local          | tr ',' '\n'       | sort -u           | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }'     )";     apk add --virtual .httpd-rundeps $runDeps;  apk del .build-deps
PASS    - DKL-DI-0005: Clear apt-get caches
PASS    - DKL-DI-0006: Avoid latest tag
FATAL   - DKL-LI-0001: Avoid empty password
    * No password user found! username : root
PASS    - DKL-LI-0002: Be unique UID
PASS    - DKL-LI-0002: Be unique GROUP

FATALが出ているのが確認できます。
最新のAlpineLinuxでは出ないものなので、定期的にチェックは必要だなと思います。

Trivy

$ trivy httpd:2.4-alpine
2019-07-16T00:48:09.514+0900    INFO    Updating vulnerability database...
2019-07-16T00:48:13.096+0900    INFO    Detecting Alpine vulnerabilities...

httpd:2.4-alpine (alpine 3.7.1)
===============================
Total: 13 (UNKNOWN: 0, LOW: 2, MEDIUM: 5, HIGH: 5, CRITICAL: 1)

+------------+------------------+----------+-------------------+---------------+--------------------------------+
|  LIBRARY   | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION |             TITLE              |
+------------+------------------+----------+-------------------+---------------+--------------------------------+
| bzip2      | CVE-2019-12900   | HIGH     | 1.0.6-r6          | 1.0.6-r7      | bzip2: out-of-bounds write in  |
|            |                  |          |                   |               | function BZ2_decompress        |
+------------+------------------+          +-------------------+---------------+--------------------------------+
| expat      | CVE-2018-20843   |          | 2.2.5-r0          | 2.2.7-r0      | expat: large number of colons  |
|            |                  |          |                   |               | in input makes parser consume  |
|            |                  |          |                   |               | high amount...                 |
+------------+------------------+----------+-------------------+---------------+--------------------------------+
| libxml2    | CVE-2018-14404   | MEDIUM   | 2.9.7-r0          | 2.9.8-r1      | libxml2: NULL pointer          |
|            |                  |          |                   |               | dereference in                 |
|            |                  |          |                   |               | xpath.c:xmlXPathCompOpEval()   |
|            |                  |          |                   |               | can allow attackers to cause   |
|            |                  |          |                   |               | a...                           |
+            +------------------+          +                   +               +--------------------------------+
|            | CVE-2018-14567   |          |                   |               | libxml2: Infinite loop when    |
|            |                  |          |                   |               | --with-lzma is used allows for |
|            |                  |          |                   |               | denial of service...           |
+            +------------------+----------+                   +               +--------------------------------+
|            | CVE-2018-9251    | LOW      |                   |               | libxml2: infinite loop in      |
|            |                  |          |                   |               | xz_decomp function in xzlib.c  |
+------------+------------------+----------+-------------------+---------------+--------------------------------+
| perl       | CVE-2018-18311   | HIGH     | 5.26.2-r1         | 5.26.3-r0     | perl: Integer overflow         |
|            |                  |          |                   |               | leading to buffer overflow in  |
|            |                  |          |                   |               | Perl_my_setenv()               |
+            +------------------+          +                   +               +--------------------------------+
|            | CVE-2018-18314   |          |                   |               | perl: Heap-based buffer        |
|            |                  |          |                   |               | overflow in S_regatom()        |
+            +------------------+          +                   +               +--------------------------------+
|            | CVE-2018-18312   |          |                   |               | perl: Heap-based               |
|            |                  |          |                   |               | buffer overflow in             |
|            |                  |          |                   |               | S_handle_regex_sets()          |
+            +------------------+----------+                   +               +--------------------------------+
|            | CVE-2018-18313   | MEDIUM   |                   |               | perl: Heap-based buffer read   |
|            |                  |          |                   |               | overflow in S_grok_bslash_N()  |
+------------+------------------+----------+-------------------+---------------+--------------------------------+
| postgresql | CVE-2019-10164   | CRITICAL | 10.5-r0           | 10.9-r0       | PostgreSQL: stack-based        |
|            |                  |          |                   |               | buffer overflow via setting a  |
|            |                  |          |                   |               | password                       |
+            +------------------+----------+                   +---------------+--------------------------------+
|            | CVE-2019-10129   | MEDIUM   |                   | 10.8-r0       | postgresql: Memory disclosure  |
|            |                  |          |                   |               | in partition routing           |
+            +------------------+----------+                   +               +--------------------------------+
|            | CVE-2019-10130   | LOW      |                   |               | postgresql: Selectivity        |
|            |                  |          |                   |               | estimators bypass row security |
|            |                  |          |                   |               | policies                       |
+------------+------------------+----------+-------------------+---------------+--------------------------------+
| sqlite     | CVE-2018-20346   | MEDIUM   | 3.21.0-r1         | 3.25.3-r0     | CVE-2018-20505 CVE-2018-20506  |
|            |                  |          |                   |               | sqlite: Multiple flaws in      |
|            |                  |          |                   |               | sqlite which can be triggered  |
|            |                  |          |                   |               | via...                         |
+------------+------------------+----------+-------------------+---------------+--------------------------------+

ライブラリ毎に表示されるので見やすいですね。
CRITICALが出ているので、ここは最低でも塞いで起きたいところです。

まとめ

簡単にイメージスキャンができることがわかっていただけたと思います。
今回は素のままのコマンド実行でしたが、表示形式の変更やファイルへの出力などのオプションもあるので、うまく使って様々な箇所に応用していきましょう。

元記事はこちら

コンテナイメージのセキュリティチェック&脆弱性診断を簡易的に実施する