現場で戦うエンジニア諸君。セキュリティ対策というと、多くの人間が「複雑な暗号化アルゴリズム」や「高価なWAF」の導入に意識を向ける。だが、インシデントの現場で我々が対峙するのは、もっと泥臭い「権限の不備」だ。
今日は、コンテナ環境における「永続化」を食い止めるための、最も基本的かつ強力な防壁について語る。攻撃者がコンテナに侵入した際、彼らがまず行うのは「バックドアの設置」と「自身の痕跡を消すこと」だ。これを物理的に不可能にするのが、これから解説する設定である。
—
1. なぜ「ルート権限」と「書き込み許可」が命取りになるのか
攻撃者がWebアプリケーションの脆弱性(RCEなど)を突いてコンテナ内に侵入したとき、彼らがまず狙うのは /tmp や /var/www/html へのファイルの書き込みだ。ルート権限で動いているコンテナなら、彼らはカーネルの脆弱性を突いてホストOSへ脱出(コンテナエスケープ)したり、悪意のあるプロセスをバックグラウンドで永続的に動かしたりできる。
これを防ぐための鉄則が、「最小権限の原則」と「不変性(イミュータビリティ)」の徹底だ。
—
2. 攻撃の盲点:ファイルシステムを書き込み不可にする
多くのエンジニアは「コンテナを再起動すれば消えるから安心」と高を括る。しかし、攻撃者はその再起動までの数分間で、メモリ上にペイロードを展開したり、外部のC2サーバーと通信するバックドアを隠したりする。
readOnlyRootFilesystem を有効にすると、コンテナのルートファイルシステムは書き込み禁止になる。もし攻撃者が echo "backdoor" > /var/www/html/shell.php を実行しても、システムは冷徹に Read-only file system というエラーを突きつけ、攻撃を無効化する。
Kubernetesでの設定例(セキュリティコンテキスト)
deployment.yaml に以下の設定を記述する。これが現代のコンテナ運用における「最低限の礼儀」だ。
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
template:
spec:
securityContext:
# ルートユーザーでの実行を禁止
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
containers:
- name: app-container
securityContext:
# ルートファイルシステムを読み取り専用に
readOnlyRootFilesystem: true
# 権限昇格を許可しない
allowPrivilegeEscalation: false
capabilities:
# 不要な特権をすべて剥奪
drop:
- ALL
volumeMounts:
# 書き込みが必要なディレクトリだけをメモリ上にマウント
- name: tmp-volume
mountPath: /tmp
volumes:
- name: tmp-volume
emptyDir: {} # コンテナ再起動で消える揮発性ストレージ
—
3. 実装の壁:書き込みが必要な場合の対処法
「でも、ログ出力やキャッシュ生成で書き込みが必要な場合は?」という疑問が湧くはずだ。答えは簡単。必要な場所だけを tmpfs (メモリ上の空ディレクトリ) としてマウントする。
アプリケーションコード側でも、書き込み先をハードコードせず、環境変数で動的に制御する設計が求められる。
Node.js (Express) でのキャッシュ保存先制御例
// プロダクション環境では、書き込み可能なパスを環境変数から取得する
const cachePath = process.env.CACHE_DIR || '/tmp/app_cache';
const fs = require('fs');
try {
// 書き込みチェック
fs.writeFileSync(`${cachePath}/health.check`, 'ok');
} catch (err) {
console.error('ファイルシステムが保護されています:', err.message);
}
—
4. なぜ今、この設定が重要なのか
昨今の攻撃者は、高度なゼロデイ攻撃を毎回使うわけではない。多くの場合、設定の甘いコンテナを見つけ、そこから自動化されたスクリプトで侵害を広げる。
- ルート権限を剥奪する: 攻撃者がカーネルの脆弱性を突くハードルを劇的に上げる。
- ReadOnlyRootFilesystemを設定する: 攻撃者が攻撃コードを保存する場所を物理的に奪う。
この2つを組み合わせるだけで、攻撃者は「侵入はできても、何も残せず、何も変えられない」という無力な状態に陥る。
—
セキュリティチーフからの提言
いいか、セキュリティとは「完璧な壁」を作ることではない。「攻撃者がコストを支払う価値がないと思わせること」だ。
ReadOnlyRootFilesystemを導入すると、確かに開発初期は「Permission Denied」との戦いになるかもしれない。だが、その手間こそが、将来のインシデント対応で徹夜するリスクを回避するための「プレミアムな保険」だ。
明日からのデプロイメント定義に、上記の securityContext が記述されているか、必ず確認してほしい。守るべきはサーバーのスペックではなく、我々が必死に育ててきたアプリケーションと、その先にある顧客の信頼だ。
健闘を祈る。
コメント