コンテナ権限昇格脆弱性に関するガイド
コンテナの多くの利点の1つは、隔離性です。
Linuxのnamespaceとcgroupを慎重に利用することで、コンテナはプロセスを互いに独立して実行できるサンドボックスを作成します。これは、優れた開発者体験と相まって、エンジニアとセキュリティ担当者の両方の間でコンテナの人気が高まることにつながりました。
しかし、それは本当に十分でしょうか?
そして、ワークロードのいずれかが侵害された場合でも、安全であると断言できますか?
さて、この記事では、攻撃者にとっての夢であり、システム管理者にとっての悪夢である事象を取り上げ、それがどのように、なぜ発生するのか、そしてHacker Newsのトップページに載る前にそれを防ぐ方法を説明します。Dockerコンテナのセキュリティ脆弱性についてさらに詳しく知るには、Dockerコンテナのセキュリティ脆弱性トップ9をご覧ください。
コンテナ分離とそのセキュリティギャップ
コンテナに関して見落とされがちな重要な点は、それらがすべて同じホストを共有していることです。これは、各コンテナのセキュリティレベルが隣接するコンテナと同等であることを意味します。ホストまたは単一のコンテナが侵害された場合、インフラストラクチャにとって壊滅的な事態を招く可能性があります。
だからといって、コンテナを完全に排除すべきではありません。そうではありません。ここでのポイントは、詳細を理解することです。
攻撃者が権限昇格の方法を見つけると、分離が破綻し始めます。権限昇格は、攻撃的セキュリティ攻撃において最もスリリングでありながらも困難な動きの一つです。それは、低い権限の足がかりを完全なシステム侵害へと変えます。
コンテナの特権昇格の脆弱性は、セキュリティ境界の回避を容易にし、より大きな成果をもたらします。なぜなら、単一のコンテナだけでホストへのアクセスが可能になるからです。
これは単なる理論ではありません。悪名高い2019年のrunCの脆弱性(CVE-2019-5736)は、攻撃者がホストのrunCバイナリを上書きし、ホストのrootアクセスを奪取することを可能にしました。
これらの攻撃は一過性のものではありません。そのため、次のような疑問に直面するかもしれません:

最近では、CVE-2024-21626のような脆弱性や巧妙なレイヤーベースの攻撃により、Linuxのcapabilities、マウント、またはファイルシステムレイヤリングを誤用すると、非rootコンテナでさえ危険になり得ることが示されています。
そのため、Dockerの脆弱性に関する以前のガイドを基に、これらの攻撃がどのように機能するのか、増大するリスクを浮き彫りにする最近のCVE、そしてそれらを防ぐ方法について探っていきます。
最近のコンテナ特権昇格の脆弱性
すべてのコンテナの脆弱性が特権昇格につながるわけではありませんが、最も危険なものはしばしばそうなる傾向があります。一部の脆弱性はコンテナの分離を回避し、他に類を見ないほどの高い特権を奪取します。
以下は、最近のコンテナの権限昇格の脆弱性3つです。
CVE-2024-21626: runC Working Directory Breakout
CVE-2024-21626は、広く使用されているコンテナランタイムであるrunCに影響を与える高深刻度のコンテナエスケープ脆弱性です。この脆弱性は、execveとPR_SET_NO_NEW_PRIVS prctlコールが安全でない方法で使用されることに起因し、コンテナが過度に緩いマウントやケーパビリティで起動された場合に、攻撃者がno_new_privsフラグをバイパスすることを可能にします。
no_new_privsフラグは、多くのコンテナエンジンがサンドボックスのために依存している特権昇格に対する主要な緩和策の1つです。
この脆弱性はrunCのv1.1.11以降のバージョンで導入され、v1.1.12で修正されました。
Ubuntu Kernel OverlayFS ModuleにおけるCVE-2023-2640およびCVE-2023-32629
OverlayFSは、コンテナとKubernetesの基本的な構成要素の一つです。単一のLinuxホスト上で2つのディレクトリ(lowerdirとupperdir)を重ね合わせ、それらを単一のディレクトリとして提示することで、docker buildやdocker commitなどのレイヤー関連のDockerコマンドのパフォーマンスを向上させます。
CVE-2023-2640および CVE-2023-32629は、悪意のある攻撃者がボリュームマウントを持つ非ルート特権コンテナを使用して特権を昇格させることを可能にします。これは、ボリュームマウントが個別のディスクとして扱われ、コンテナ層の外部に存在するOverlayFSを作成するために使用できるためです。これにより、攻撃者は標準のコンテナ制限をバイパスし、マウントされたボリューム上の拡張ファイル属性を操作できます。これらの属性は、昇格された権限(CAP_SETUIDやCAP_SYS_ADMINなど)がそのままの状態で上位層にコピーされます。
CVE-2022-0492
2022年3月4日、セキュリティ研究者は、Linuxカーネルのcgroup_release_agent_writeに重大な欠陥を発見しました。これは、コンテナエスケープを可能にし、コンテナが実行されているノード全体を制御する可能性がありました。
クラウドプロバイダーとLinuxディストリビューションは迅速に対応しました。パッチが発行され、アドバイザリが公開されました。しかし、その裏には複雑さが残っていました。他のソフトウェアとは異なり、Linuxカーネルにはディストリビューション間で標準化されたバージョン管理スキームがありません。
つまり、これらのパッチを適用し、ベースイメージをアップグレードするには数ヶ月かかりました。その間、環境は潜在的な攻撃に対して脆弱なままでした。
DockerおよびDocker Composeにおける権限昇格の防止
Dockerコンテナ内からの権限昇格攻撃を防ぐ最善の方法は、コンテナのアプリケーションを非特権ユーザーとして実行するように構成し、コンテナが特権アクセスを持たないようにすることです。
実際には、Dockerでの権限昇格を防ぐために、以下を行う必要があります。
- 権限昇格のロック
- 適切な非rootユーザーを設定する
- Drop Linuxカーネルケーパビリティ
オプションは、ランタイム時、またはDocker Composeの設定内の2つです。
ランタイム時に、以下のフラグを使用します:
docker run \
--read-only \
--security-opt=no-new-privileges \
--network your-isolated-network \
--cap-drop ALL
--cap-add CHOWN \
--pids-limit 99 \
--user=your-user \ # your non-root user.
... # OTHER OPTIONS GO HERE
your-app:v1.0.1Docker Composeを使用すると、設定は次のようになります。
services:
webapp:
image: your-app:v1.0.1
read_only: true
security_opt:
- no-new-privileges:true
networks:
- your-isolated-network
cap_drop:
- ALL
cap_add:
- CHOWN
# その他のオプションはこちら
...rootユーザーアクセスを必要とするコンテナの実行
(システムユーティリティの実行、レガシーサービスなど) コンテナが実行にrootアクセスを必要とする場合があります。そのような場合、このユーザーをDockerホスト上のより権限の低いユーザーに再マッピングできます。
コンテナのUIDを再マッピングすることで、コンテナがrootとして実行されていると考えていても、ホストの観点からは実際には非特権ユーザーとして動作することが保証されます。これにより、コンテナブレイクアウトやホスト侵害のリスクが軽減されます。
Dockerのドキュメントでユーザー名前空間のリマッピングに関する詳細な手順とベストプラクティスを参照できます。
Kubernetesにおける権限昇格の防止
Kubernetesでは、権限昇格を防ぐ最善の方法は、コンテナ作成時にspec.containers.securityContext設定を使用することです。これは、過剰な権限を持つコンテナを作成すると、ランタイムで更新できず、削除して再作成する必要があるためです。
以下のsecurityContextは、上記で述べたように、パッチが適用されていないCVE-2022-0492のLinuxバージョンであっても、アプリコンテナを権限昇格から保護します。
containers:
- name: app
image: myapp:bullseye-20230912
securityContext:
runAsUser: 1000 # 非rootユーザーIDを使用
runAsGroup: 1000 # 非rootグループIDを使用
runAsNonRoot: true # 非root実行を明示的に強制
allowPrivilegeEscalation: false # プロセスがより多くの権限を獲得するのを防ぐ
readOnlyRootFilesystem: true # ルートファイルシステムへの書き込みを禁止
capabilities:
drop:
- ALL # 攻撃対象領域を最小化するため、すべてのケーパビリティを削除
add:
- NET_BIND_SERVICE # アプリケーションが必要とするケーパビリティのみを追加
seccompProfile:
type: RuntimeDefault # コンテナランタイムのデフォルトseccompプロファイルを使用Kubernetesにおける権限昇格を防ぐもう一つの優れた方法は、セキュリティコンテキストを持たないPodが全くスケジュールされないようにすることです。KubernetesのAdmission Controllerは、APIサーバーへのリクエスト(例えばデプロイメント)を傍受し、処理される前に特定の条件が満たされていることを検証することを可能にします。
より高度なユースケースでは、アドミッションコントローラーをゼロから記述できます。しかし、Kyvernoのようなオープンソースソリューションを使用すると、セキュリティコンテキストが設定されていないデプロイメントをパッチ適用できるクラスター全体のポリシーを記述できます。これは通常、次のようになります。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-securitycontext
annotations:
policies.kyverno.io/title: デフォルトのsecurityContextを追加
policies.kyverno.io/category: サンプル
policies.kyverno.io/subject: Pod
policies.kyverno.io/minversion: 1.6.0
policies.kyverno.io/description: >-
PodのsecurityContextエントリは、Podを実行するために使用されるユーザーとグループなどのフィールドを定義します。
Podの定義を妨げないためには、ブロックするのではなく、ユーザーのデフォルト値を選択する方が良い場合があります。このポリシーは、PodのsecurityContext内の`runAsNonRoot`、`runAsUser`、`runAsGroup`、および `fsGroup`フィールドがまだ設定されていない場合に、それらを設定するようにPodをミューテートします。
spec:
rules:
- name: add-default-securitycontext
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
securityContext:
+(runAsNonRoot): true
+(runAsUser): 1000
+(runAsGroup): 3000
+(fsGroup): 2000上記のマニフェストは、ClusterPolicyカスタムリソース定義(CRD)を使用し、mutate.PatchStrategicMergeと非rootユーザーのセキュリティコンテキストを介してPodをパッチ適用します。
セキュアなコンテナは構築されるものであり、前提とされるものではありません。
コンテナは分離性を提供し、アプリケーションを構築・実行するための信頼性の高い方法ですが、コンテナセキュリティの絶えず変化する性質は、予防措置のみに頼ることはできないことを意味します。代わりに、インシデント発生時に迅速に対応できる戦略を採用すべきです。
その戦略は、プロアクティブなスキャンから始まります。
Aikidoを使用すると、本番環境に到達する前に、コンテナイメージを自動的にスキャンして、脆弱なパッケージ、古いランタイム、またはリスクのあるDockerfile命令を検出できます。そのオープンソースの依存関係スキャナーは、ライブラリにエクスプロイト可能なCVEや、攻撃者が権限昇格に利用する可能性のある密かにパッチが適用された脆弱性がないかをチェックすることで、この保護を拡張します。
これらのスキャンは、安全でないKubernetesまたはDockerの設定を検出するIaCおよび構成チェックと、パイプラインレベルで最小権限ポリシーを強制するCI/CDセキュリティゲートによって強化されます。そして、予防策が完璧であることは決してないため、Aikido Zenはランタイム保護を追加し、異常なコンテナの動作をリアルタイムで検出して対応します。

