
なぜDockerイメージに脆弱性が残るか
DockerfileのFROM行はビルドの起点であり、その上に不変(immutable)の層が積み重なって最終的なイメージが作られる。FROM node:24のように公式イメージを指定した場合、ランタイムに加えてDebian系のシェルやパッケージマネージャ、システムライブラリがそのまま取り込まれる。各命令は新しいレイヤーを追加するだけであり、後のレイヤーでrmしても実態としては残り、攻撃対象領域を縮める効果は限定的である。
Aikidoは、不要なパッケージを削除してもアプリが実際に依存するライブラリの脆弱性は別途パッチが必要であり、最小化したイメージでも新規CVEの蓄積は止まらないと結論づけている。すなわち「ハードニング」はビルド時点の施策ではなく、出荷後の継続的な脆弱性管理に帰着する。
Looney Tunablesを教訓にする
具体例としてCVE-2023-4911、通称「Looney Tunables」が挙げられている。glibcの動的ローダーに存在したローカル権限昇格の欠陥で、主要なLinuxイメージのベースレイヤーにほぼ共通して含まれるコンポーネントが起点であった。Debian系のフルディストリビューション上でビルドされたイメージであれば、当該glibcを継承していた可能性が高い。
一方、Alpineはmuslベースで設計されており、この脆弱性の影響を受けなかったとされる。ベースイメージの選択は表面上マイナーバージョンの差に見えても、採用しているCライブラリや同梱ユーザーランドの差が、脆弱性の露出範囲を実質的に左右する。
# ベース選定の例: 同じランタイムでも取り込まれるライブラリ層が異なる
FROM php:8.3-cli # Debianベース(glibc)
# FROM php:8.3-cli-alpine # muslベース個人開発環境で実施すべき確認
ローカルに存在するベースイメージのダイジェストとバージョンを棚卸しする。PHP/Laravelのランタイムを載せている場合、glibc、openssl、libxmlといった共通ライブラリはイメージ側にバンドルされ、PHPパッケージ側の更新だけでは追従しない。
# ローカルのイメージ一覧をダイジェスト付きで表示
docker images --digestsFROMの更新は、イメージのメジャーバージョン切り替えを伴わないとは限らない。Aikidoはピン留めしたバージョンに対してパッチ済みビルドをPRとして配信する方式を提示しており、メジャーバージョンへの強制移行なしに脆弱性のみを是正する選択肢となる。ベース選定のトレードオフは以下の通り。
- フルディストリビューション: デバッグ容易、aptで追加導入が楽。攻撃対象領域は最大。
- slim: 容量と表面積を削減。ただしglibc依存の脆弱性は継承される。
- distroless: シェル・パッケージマネージャを削除し最小。コンテナ内デバッグは困難。
- Alpine (musl): 軽量で一部CVEを回避可能。PHP拡張のビルド済みwheelが揃わない場合は自前ビルドが必要。
ハードニングの本質は、一度ビルドした時点で「安全」が確定する点にあるのではなく、新たなCVE公表のたびに再評価する運用設計の問題である。ベースイメージの選択、脆弱性の継続スキャン、パッチ追従のフローをCIに組み込むかが、個人開発レベルでも実装すべき分岐点となる。
関連記事: Docker・インフラ構築をわかりやすく解説 、 LaravelのDockerイメージを軽量化するマルチステージビルドの設計手順.