読み取り専用ルートファイルシステム:コンテナ脱獄を封じる「最後の砦」
セキュリティを語る際、多くのエンジニアが境界防御やWAF、あるいは最新のEASM(外部攻撃対象領域管理)に目を奪われがちだ。しかし、真のプロフェッショナルは知っている。攻撃者が足場を築いた瞬間に戦場は「コンテナ内部」に移るということを。
今日議論するのは、Kubernetes環境における readOnlyRootFilesystem の強制だ。これは単なる「設定項目」ではない。攻撃者が最も好む「侵入後の永続化(Persistence)」と「ツールセットの展開」という戦術を、物理的に遮断するためのアーキテクチャ上の急所である。
なぜ攻撃者は「書き込み権限」に執着するのか
攻撃者がコンテナへの初期侵入(RCEなど)に成功した際、まず彼らが行うのは、自身の武器庫(ペイロード)の展開だ。curl や wget でマルウェアをダウンロードし、/tmp や /var/tmp、あるいは既存のバイナリを書き換えてバックドアを仕込む。
もしルートファイルシステムが読み取り専用であれば、彼らはメモリ上のみで完結する「ファイルレス攻撃」を強いられる。これは攻撃の難易度を劇的に引き上げる。メモリ空間だけで完結するエクスプロイトは、再起動やコンテナの再デプロイによって消失するからだ。攻撃者にとって、書き込み不可のコンテナは「砂上の楼閣」であり、決して信頼できない足場なのだ。
守りのアーキテクチャ:Read-Only化の実装
Kubernetesでこれを実現するのは難しくない。しかし、アプリケーションが書き込みを必要とする領域(ログ、キャッシュ、アップロード先)との戦いが待っている。
以下は、SecurityContext を用いた最小権限の構成例だ。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hardened-app
spec:
template:
spec:
containers:
- name: app-container
image: secure-app:v1.0
securityContext:
# ルートファイルシステムを読み取り専用に
readOnlyRootFilesystem: true
# 特権昇格を禁止
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
# 書き込みが必要な場所だけ、メモリベースの空ディレクトリをマウントする
- mountPath: /tmp
name: tmp-volume
volumes:
- name: tmp-volume
emptyDir:
medium: Memory # ディスクIOを回避し、かつ再起動で自動消去される
盲点:なぜ「完全な防御」には至らないのか
readOnlyRootFilesystem: true は強力だが、銀の弾丸ではない。我々レッドチームの視点から言えば、この設定があっても突破口は存在する。
1. エフェメラルストレージの乱用: emptyDir でマウントされたパスは書き込み可能である。もしアプリケーションが tmp ディレクトリへの書き込みを許容しているなら、攻撃者はそこにスクリプトを配置し、実行権限を与えて動作させる可能性がある。noexec オプションをマウント時に付与するのが定石だ。
# セキュリティ制約を強化したマウント設定
volumeMounts:
- mountPath: /tmp
name: tmp-volume
# 実際にはPodSecurityPolicyやAdmission Controllerで制御する
2. プロセスメモリへのコード注入: ルートFSが読み取り専用でも、実行中のプロセスに対して ptrace や gdb を用いてメモリを書き換える攻撃は依然として可能だ。これには capabilities の制限(CAP_SYS_PTRACE の削除)が不可欠である。
3. Kernel脆弱性の悪用: 最終的にコンテナはカーネルを共有している。dirty COW のようなカーネル脆弱性を突かれた場合、ファイルシステムの読み取り属性など無力化される。コンテナセキュリティは「カーネルの硬化」とセットでなければならない。
チーフホワイトハッカーとしての提言:監査の自動化
設定を記述して満足してはならない。私は常に「ガードレイル」の自動化を推奨する。Policy-as-Code(KyvernoやOPA Gatekeeper)を用いて、ルートファイルシステムが書き込み可能になっているコンテナのデプロイをCI/CDパイプライン上で即座に拒否させるべきだ。
# Kyvernoによるポリシー強制例
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-readonly-rootfs
spec:
validationFailureAction: Enforce
rules:
- name: check-readonly-rootfs
match:
resources:
kinds:
- Pod
validate:
message: "ルートファイルシステムは読み取り専用でなければなりません。"
pattern:
spec:
containers:
- readOnlyRootFilesystem: true
結び:防御は「不便さ」との妥協点にある
真にセキュアなシステムとは、攻撃者にとって「極めて居心地の悪い」環境だ。書き込みを禁止し、メモリを制限し、実行権限を剥奪する。開発チームからは「運用が面倒だ」という不満が出るだろう。しかし、その「不便さ」こそが、インシデント発生時のフォレンジックにおいて、攻撃者が何も残せず、何も配置できないという「結果」に直結する。
システムアーキテクトとして、この制約をデフォルト(デフォルト・拒否)に設定することは、あなたの環境における「攻撃者の滞在時間」を劇的に短縮させるための最もコストパフォーマンスの高い投資なのだ。
さあ、あなたの環境の Deployment マニフェストを確認してみてほしい。そこにはまだ、攻撃者のための「書き込み可能な隙間」が残されていないだろうか。
コメント