現場で数え切れないほどの「やらかし」の後始末をしてきた経験から言わせてもらうと、「動けばいいや」でデプロイしたコンテナイメージは、攻撃者にとっての『宝の山』だ。
多くの開発者が、開発環境と同じ感覚で FROM node:18 や FROM python:3.11 をそのまま本番用イメージに使っている。だが、そこに何が入っているか考えたことはあるか? git、curl、gcc、make……。これらはアプリケーションを動かすために必要なのか? 答えは「NO」だ。
攻撃者がコンテナに侵入した瞬間、これらのツールが揃っているか否かで、その後の被害規模が180度変わる。今日は、インフラ・ネットワークの要塞化の第一歩として、マルチステージビルドによる攻撃対象領域(アタックサーフェス)の最小化について、叩き込んでいく。
—
1. なぜ「重いイメージ」は罪なのか?(攻撃者の視点)
攻撃者がコンテナ内でシェル(sh や bash)を奪取した際、真っ先に彼らがやるのは「環境調査」だ。
which gccでコンパイラを探す(エクスプロイトコードをコンパイルするため)curlやwgetで外部のC2サーバーからバックドアを落とすapt-getやapkでツールを追加インストールする
もし、君のコンテナにこれらのバイナリが一切入っていなかったら? 攻撃者はその場で詰む。これが「最小限の実行環境」を構築する最大の理由だ。
2. 実践:マルチステージビルドの極意
マルチステージビルドとは、「ビルドする部屋」と「実行する部屋」を分ける手法だ。ビルドに必要な重いツール群は「ビルド用の部屋」に閉じ込め、最終的なイメージには「成果物のみ」をコピーする。
実例:Node.jsアプリケーションのセキュアなDockerfile
以下は、Node.jsアプリケーションを例にした、実務でそのまま使える構成だ。
# ステージ1: ビルド環境(ここにはソースコードやビルドツールが存在する)
FROM node:18-alpine AS builder
# 作業ディレクトリの設定
WORKDIR /app
# 依存関係のインストール(package-lock.jsonがあると再現性が保てる)
COPY package*.json ./
RUN npm ci
# ソースコードをコピーしてビルド
COPY . .
RUN npm run build
# ステージ2: 実行環境(ここには実行に必要な最小限のものしか置かない)
FROM node:18-alpine AS runner
# 実行ユーザーをroot以外にする(特権昇格対策)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
# ビルドステージから生成物のみをコピー
# ソースコードやnode_modulesの不要なファイルは含まれない
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
# ポートを指定
EXPOSE 3000
# 非rootユーザーで実行
CMD ["node", "dist/main.js"]
この構成の何が優れているのか?
1. ソースコードの隠蔽: 実行環境にはソース(.ts 等)が含まれないため、万が一イメージが流出してもロジックが露呈しにくい。
2. バイナリ排除: npm も gcc も git も含まれない。攻撃者が侵入しても、npm install などのコマンドは実行不能だ。
3. 特権の剥奪: USER appuser を指定することで、コンテナ内で何らかの脆弱性を突かれても、ホストOSを直接乗っ取られるリスク(コンテナエスケープ)を大幅に下げている。
—
3. さらに一歩先へ:Distroless イメージの検討
もし君がGoやPython、Javaを使っているなら、alpine すら使わない gcr.io/distroless という選択肢がある。これはOSのシェルすら存在しない、まさに「実行ファイルのみ」のイメージだ。
「シェルがない」ことは、攻撃者にとって最強の防御になる。 侵入しても ls も cat も打てないのだから、偵察が不可能になる。
—
4. 運用エンジニアへの提言
イメージを小さくすることは、セキュリティだけではなく、デプロイ時間の短縮という運用上のメリットももたらす。
- 脆弱性スキャンの精度向上: イメージ内のパッケージが減れば、不要な警告(False Positive)も減り、本当に直すべき脆弱性に集中できる。
- CI/CDパイプラインの高速化: 軽いイメージは転送が速い。
現場で守るべき「3つの掟」
1. latest タグを使うな: 常に特定のバージョン(node:18.17.0-alpine 等)を指定し、意図せぬ変更を排除せよ。
2. root で走らせるな: コンテナ内での UID 0 は、事故の元だ。
3. 定期的な脆弱性診断: Trivy や Snyk といったツールをCIに組み込み、イメージが作られた瞬間に脆弱性がないかチェックするフローを自動化せよ。
セキュリティは「魔法の杖」ではなく「日々の積み重ね」だ。今日紹介したマルチステージビルドは、君が明日から導入できる最もコスト対効果の高い防御策の一つだ。まずは手元の Dockerfile を build ステージと runner ステージに分割することから始めてみてくれ。
健闘を祈る。何かあればいつでも相談してくれ。
コメント