Skip to content

This tool is not affiliated with, endorsed by or sponsored by Google LLC. Google Cloud and Google Cloud Platform are trademarks of Google LLC. Other names are trademarks of their respective owners.

GCS and BigQuery exfiltration: evidence in audit logs

Prove or rule out data theft from Cloud Storage and BigQuery: bulk storage.objects.get, public buckets, HMAC keys, cross-project copies, shared disk images.

Published on 5 min read

TL;DR. Data theft from Google Cloud shows in four places: reads (storage.objects.get bursts, only with Data Access logs), exposure (allUsers added to a bucket or dataset policy, always logged), copies to another project (BigQuery jobs with a foreign destination, shared images and snapshots), and side-door credentials (HMAC keys). Establish the exposure window from the Admin Activity logs even when reads are invisible, and say so explicitly in the report.

"Was data taken?" is the question legal and management will ask first, and the one audit logs answer least completely. Here is what each log can and cannot prove.

Reads: storage.objects.get

Every object download through the Cloud Storage JSON or XML API is a storage.objects.get call, recorded as a Data Access (DATA_READ) audit log if Data Access logging was enabled for Cloud Storage (Cloud Storage audit logging). The entry gives the object in resourceName (projects/_/buckets/BUCKET/objects/NAME), the principal, the caller IP and the user agent.

logName:"cloudaudit.googleapis.com%2Fdata_access"
protoPayload.methodName="storage.objects.get"
resource.labels.bucket_name="BUCKET"

What to extract:

  • Volume and rate per principal: a hundred objects in a few minutes by a principal that normally reads four a day.
  • Object names: they tell you what was taken (customers/…, backups/…).
  • The principal: a consumer account, a service account key from an unknown IP, or a service agent doing its normal job.
  • Also storage.objects.list (discovery before download) and, if the attacker copies rather than downloads, storage.objects.create / rewrite in a destination bucket they control.

This maps to T1530, Data from Cloud Storage. The caveat is heavy: Data Access logs are off by default, and Google notes that enabling them for Cloud Storage can break authenticated browser downloads in some cases (configure Data Access audit logs), which is why many projects never turn them on. See Data Access logs limitations.

Exposure: allUsers and allAuthenticatedUsers

Making a bucket public is a policy change, therefore Admin Activity, therefore always logged. For buckets the method is storage.setIamPermissions; the delta is in serviceData.policyDelta.bindingDeltas:

{ "action": "ADD", "role": "roles/storage.objectViewer", "member": "allUsers" }

Once allUsers can read a bucket, anonymous downloads are possible and you will not see them: Google states that Cloud Audit Logs does not track access to public objects (Cloud Storage audit logging). Only Cloud Storage usage logs, if they were configured, record that traffic. The honest conclusion is an exposure window: from the ADD to the REMOVE (or to now), the content was available to anyone who knew or guessed the bucket name. That window, and the list of objects in the bucket during it, is what a data-protection assessment needs. Google's making data public page explains the mechanism; public access prevention blocks it.

The same logic applies to allUsers on BigQuery datasets, Cloud Run services, Cloud Functions or images.

Side doors: HMAC keys

storage.hmacKeys.create creates an access key / secret pair for the S3-compatible XML API. It is persistence rather than exfiltration: the key keeps working after you rotate the service account's other credentials. Treat every HMAC key created during the incident as attacker-controlled (T1098.001).

BigQuery: copies to another project

BigQuery is different in one useful way: Data Access audit logs are enabled by default for some BigQuery services (Cloud Audit Logs overview). Jobs (queries, copies, extracts) are recorded with their configuration (BigQuery audit logs).

The signal to look for is a destination outside the project: a copy job or query job whose destinationTable belongs to another project ID. An attacker with read access to your dataset and write access to their own project can move a whole table in one call: T1537, Transfer Data to Cloud Account. Also review extract jobs (destinationUris pointing to gs:// buckets you do not own) and dataset policy changes (metadata.datasetChange.bindingDeltas).

Disk images and snapshots

Sharing a disk image or snapshot with another project copies the whole disk out, databases and credentials included. Look for setIamPolicy on compute.images, compute.snapshots, compute.disks and compute.machineImages, then check which members were added.

Network egress

Data read by a compromised VM and sent out does not appear in audit logs at all. VPC Flow Logs are the only source: bytes sent per external IP.

What the analyzer flags

RuleSeverityCondition
bulk_object_readsHigh100 storage.objects.get by the same principal within 10 minutes (any outcome)
public_access_grantedCriticalallUsers / allAuthenticatedUsers added to any IAM policy
bq_foreign_exportHighBigQuery copy or query job whose destination table is in another project
image_sharedMediumIAM policy of an image, snapshot, disk or machine image changed
hmac_key_createdMediumstorage.hmacKeys.create
flow_large_egress (analytic)Medium1 GiB or more sent from your VMs to one public IP (VPC Flow Logs)

Limits of the current rules: BigQuery extract jobs to external buckets are not flagged yet, and a busy ETL service account reading steadily can trip the bulk-read threshold. The tool also warns when an export contains no Data Access logs, since bulk_object_reads cannot fire without them.

Writing it up

For each dataset or bucket involved, state separately: what was proven read (object names, principal, time), what was exposed (window, objects present), what was copied (destination project, table, image), and what cannot be known because of missing logs. The remediation checklist in the tool prompts for this assessment, since notification duties such as GDPR's 72-hour rule can depend on it.

Frequently asked questions

Can I see which files were downloaded from a Cloud Storage bucket?

Only if Data Access audit logs (DATA_READ) were enabled for Cloud Storage before the download. Each read then produces a storage.objects.get entry with the object name, the principal and the caller IP. Without them, reads are not recorded in audit logs.

How do I know if a bucket was made public?

Look for Admin Activity entries (storage.setIamPermissions for buckets) whose policy delta adds allUsers or allAuthenticatedUsers. Admin Activity logs are always on, so this change is recorded even when reads are not.

Further reading

Related articles

This tool is not affiliated with, endorsed by or sponsored by Google LLC. Google Cloud and Google Cloud Platform are trademarks of Google LLC. Other names are trademarks of their respective owners.