// HackTheBox  ·  writeup

Google Cloud Compromise

HackTheBox Medium Pythongrepipinfo.io
Cloud

Scenario

Analyse Google Cloud logs to determine the malicious actions of an insider.

Executive Summary

A service account in a crypto startup’s Google Cloud project authenticated from a single foreign IP, using a Mac and the gsutil client. Its first move was an API call that was denied, an attempt to enable a service it had no rights over. It then switched to storage, listed the contents of one bucket, and downloaded a single object from it. The whole intrusion sits in one Cloud Audit Log export, and the identity, source address and user agent stay constant from the first event to the last.

Artifacts

Format note: The answer masks are strict. Each * is one character, so a mask gives the exact length of the answer. The API call in Q6 is a fully qualified method name, not a short label.

Initial Access

Q1 — What’s the name of the project that was compromised?

Read the first entries of the export. Every record is tagged with its project in resource.labels.project_id, and the file holds only one value.

head -c 3000 evidence/gcp.json
Click flag to reveal cryptostartup
Q2 — What google cloud identity is compromised?

The acting identity is in authenticationInfo.principalEmail. One principal appears on every event, and it sits in the project’s iam.gserviceaccount.com domain, so a service account credential was leaked or abused rather than a human login.

grep -o '"principalEmail": *"[^"]*"' evidence/gcp.json | sort | uniq -c
Click to reveal answer [email protected]
Q3 — What IP address is the identity authenticated from?

From Q2 we know the identity. Count requestMetadata.callerIp across its events. Every call comes from the same external address.

grep -o '"callerIp": *"[^"]*"' evidence/gcp.json | sort | uniq -c
Click flag to reveal 178.132.108.38
Q4 — What country does the IP originate from?

Cloud Audit Logs carry no geolocation, so the address from Q3 has to go to an IP intelligence service. ipinfo.io returns the country code RO for it, which is Romania.

curl -s https://ipinfo.io/178.132.108.38/json

Gotcha: The mask fits many countries, and guessing from the look of the address is a trap. Look it up.

Click to reveal answer Romania
Q5 — What device did the attack come from?

The only device hint in Cloud Audit Logs is requestMetadata.callerSuppliedUserAgent. Here it is a gsutil string whose platform token is darwin, which is macOS. That points to a Mac, and the nine-character mask fits Macintosh. This answer was reasoned from the darwin token and the mask rather than read as a device name in the log.

grep -o '"callerSuppliedUserAgent": *"[^"]*"' evidence/gcp.json | sort | uniq -c

Tip: The user agent never says “Mac” outright. Map darwin to macOS, then fit the answer to the mask.

Click flag to reveal Macintosh

Discovery

Q6 — What was the first failed API call made by this identity?

Sort the events by timestamp and print method, status, caller IP, user agent and resource for each. The first event is a Service Usage call that returns status code 7 (permission denied). The service account tried to enable an API on the project and was refused, then fell back to what it could already reach.

python3 -I sort_events.py evidence/gcp.json

A quicker check without sorting is to list methods next to status codes and read the top of the file.

grep -oE '"methodName": *"[^"]*"|"code": *[0-9]+' evidence/gcp.json | head -20
Click to reveal answer google.api.serviceusage.v1.ServiceUsage.EnableService
Q7 — What storage bucket was enumerated?

After the denied call the identity issues storage.objects.list. The resourceName on that event names the bucket being listed.

grep -B2 -A12 'storage.objects.list' evidence/gcp.json | grep -o '"resourceName": *"[^"]*"'
Click flag to reveal importantbucket

Collection

Q8 — What API call was used to exfiltrate an item from this bucket?

The listing from Q7 is followed by a read of one object. In storage audit logs a download is storage.objects.get, not list, so that is the call that moved data out of the bucket. Counting the storage methods shows it appears once.

grep -o '"methodName": *"storage[^"]*"' evidence/gcp.json | sort | uniq -c
Click to reveal answer storage.objects.get
Q9 — Which Google Cloud command-line tool was used during the exfiltration attempt?

The user agent on the storage.objects.get event from Q8 identifies the client. It is a gsutil string, consistent with a gsutil cp download, and carries the same darwin token used in Q5.

grep -A20 'storage.objects.get' evidence/gcp.json | grep -o '"callerSuppliedUserAgent": *"[^"]*"'
Click flag to reveal gsutil
Q10 — What is the name of the file that was exfiltrated from the storage bucket?

The resourceName on the storage.objects.get event is the full object path inside the bucket from Q7. The last path segment is the file name.

grep -A20 'storage.objects.get' evidence/gcp.json | grep -o '"resourceName": *"[^"]*"'
Click flag to reveal secretcode.java

Attack Summary

PhaseAction
Initial AccessService account cloud-storage-helper authenticates from a Romanian IP using gsutil on a Mac
DiscoveryFirst call, ServiceUsage.EnableService, is denied
Discoverystorage.objects.list enumerates importantbucket
Collectionstorage.objects.get via gsutil downloads secretcode.java

IOCs

TypeValue
IP178.132.108[.]38
Service account[email protected]
Projectcryptostartup
Bucketimportantbucket
Filesecretcode.java
User agentgsutil on darwin

MITRE ATT&CK

TechniqueIDDescription
Valid Accounts: Cloud AccountsT1078.004Service account credential used from an external IP
Cloud Storage Object DiscoveryT1619storage.objects.list against importantbucket
Data from Cloud StorageT1530storage.objects.get through gsutil pulled secretcode.java

Defender Takeaways

A service account used from a foreign IP is an alert. Workload identities normally call from Google ranges or a known egress. A callerIp outside those, paired with a desktop gsutil user agent, shows the key left the environment. Alert on service-account calls whose source is not on an allowlist.

Denied calls are an early signal. The first event was a permission-denied EnableService. A PERMISSION_DENIED from a principal that normally only touches storage is reconnaissance, and it arrives before any data moves.

Turn on Data Access audit logs for Cloud Storage. The list and get events in this case exist only because data access logging was on. Without it, the bucket listing and the download would be invisible.

Prefer short-lived credentials over exported service account keys. cloud-storage-helper was usable from a laptop, which means a copyable credential existed. Workload identity or impersonation removes that key, and a role scoped to specific buckets limits what a stolen one can read.

Conclusion

One service account credential was enough for an outsider to list a bucket and copy a source file, and nothing stopped a Mac in Romania from acting as a trusted workload. The logs recorded every step; the gap was that identity and source address were never compared.