Billed for unauthorized requests? Google Cloud Storage vs. AWS S3
A recent story highlighted how unauthorized access to an empty AWS S3 bucket can result in surprising charges. This raised the question: can unauthorized access to Google Cloud Storage (GCS) also lead to unexpected bills?
Catchup: what happened?
A bit over a week ago, an interesting Medium story popped-up into many social feeds:
It even made into Hacker News frontpage.
To sum up: a user found that unauthorized 403 requests to upload files to his AWS S3 empty bucket by other users were billed to his account, as his bucket ID was used as a default value into a popular open-source solution and thousands of deployments tried by mistake to upload backup data into his bucket.
This was quite worrisome, as could be exploited by a malicious actor to bring anyone down just knowing any of our S3 bucket IDs: not by DoS but by Denial-of-Wallet attacks.
This bore the question — would this also happen on Google Cloud Storage?
Spoiler alert: no, 403 PUT requests are not billed in GCS.
Considerations
Base scenario
The original story stated that 403 non-authorized requests to AWS S3 were billed.
We wanted to also verify whether in Google Cloud Storage 403 non-authorized PUT requests to private GCS buckets are billed or not.
Number of requests
As per the GCS pricing docs, listing and uploading objects are billed as Class A operations and downloading/reading objects as Class B operations. Pricing is per 1000 operations, so we aimed for a adequate number of requests to make sure we would notice the charges.
Requester pays disabled
In GCS and S3 you can opt-in for “requester pays”, where the user making the request is paying for it instead of the bucket’s owner, which is intended for other scenarios e.g. sharing public data, rather than e.g. public website static date. Therefore, it was intentionally disabled, as is the usual configuration.
“Always free” tier and billing exports
In Google Cloud, there is a free tier for Cloud Storage for some amount of Class A and B operations, so it would mask the costs of our requests.
Therefore, detailed usage cost data was exported to BigQuery and analyzed using SQL, where we could see the charges for operations even if they would fall in this free tier or be subjected to credits, discounts and promotions.
This cost data is exported to BigQuery with some latency (usually several hours, depending on service and SKU), so results were checked after 24 hours.
Simulating unauthorized access: the experiment
2 Google Cloud projects were created:
- gcs-unauthorized-requests
- gcs-requesting-instance
Note: Usually you would want to keep your project IDs private for security.
Billing
Billing was configured for both projects with an active, good-standing billing account, with detailed usage cost data exported to BigQuery.
Details for gcs-unauthorized-requests project
- Required roles: Cloud Storage Admin
- Enabled APIs: Cloud Storage (storage.googleapis.com), BigQuery (bigquery.googleapis.com, to analyze exported billing data, could also be run in another project)
- Cloud Storage resources: location — regional, europe-southwest1, storage class — standard
- Private bucket:
bucket-name-redacted
, labelbucket-private
Details for gcs-requesting-instance project
- Required roles: Compute Engine Admin, Service Account Admin
- Enabled APIs: Compute Engine (compute.googleapis.com)
- Networking — VPC & subnet: default
- Networking — FW rules: default VPC default rules → SSH ingress enabled, all egress enabled
- VM instance: requesting-instance
- Region & zone: europe-southwest1-a (Madrid)
- Machine type: e2-standard-16 (max. egress bandwidth of 16 Gbps)
(Would have been less expensive and recommended to use a spot VM instead of a regular one, but forgot to check the option… oops)
- VM instance OS: Debian 12 (bookworm) boot disk from the GCP public image family
- VM instance has GCP Cloud SDK already installed, as by default in (most) GCE images
- VM instance with an ephemeral external IP
- User-managed service account assigned to the VM without any roles/permissions assigned, to force a 403 response
The test
Test consisted in SSHing to requesting-instance
VM and running this Bash command executing a 403 PUT request to the GCS bucket 2100 times (more than the 1000 needed, just in case):
for i in {1..2100};
do
printf "\niter $i\n" && \
gsutil cp private-bucket-file.txt \
gs://gcs-unauthorized-request-private;
done
BigQuery SQL query for checking results (after 24 hours, given the expected delay for the billing export):
SELECT
project.id as project_id,
service.description as service_description,
sku.description as sku_description,
usage_start_time,
usage_end_time,
project.id,
labels,
location.region,
resource.global_name,
usage.amount,
usage.unit,
usage.amount_in_pricing_units,
usage.pricing_unit
FROM
`REDACTED.billing_export.gcp_billing_export_resource_v1_REDACTED`
WHERE
usage_start_time >= TIMESTAMP("2024-05-03")
AND usage_start_time < TIMESTAMP("2024-05-08")
AND (project.id = 'gcs-unauthorized-requests'
OR project.id = 'gcs-requesting-instance')
AND service.description = 'Cloud Storage'
ORDER BY
usage_start_time
Experiment results
Remember: we wanted to check if 403 PUT requests to a private GCS bucket are billed.
After executing the previous bash command:
- Response status code: 403
- Response: “AccessDeniedException: 403 REDACTED@gcs-requesting-instance.iam.gserviceaccount.com does not have storage.objects.create access to the Google Cloud Storage object.
Permission ‘storage.objects.create’ denied on resource (or it may not exist).” - Number of requests: 2100
- Start: 24/05/07 08:26 CEST
- End: 24/05/07 09:19 CEST
After waiting +24 hours, we ran the query in the BigQuery billing dataset.
SQL query results: No “Regional Standard Class A Operations” were shown for these projects and dates
Verdict: No charges for unauthorized access
The good news: Google Cloud Storage doesn’t bill for unauthorized PUT requests, neither to the private bucket owner nor the unauthorized requester.
What This Means for You
Unlike AWS S3, Google Cloud Storage protects you from this “Denial-of-Wallet” attack where unauthorized access can inflate your bill. This adds another layer of security and cost control for your Cloud Storage buckets.
Further Exploration
While unauthorized PUT requests are free, we plan to investigate billing for other access scenarios, including unauthorized GET requests and public bucket access attempts. In particular,
- Check if 403 GET requests to a private GCS bucket existing file are billed
- Check if 403 GET requests to a private GCS bucket non-existing file are billed
- Check if 403 PUT requests to a public GCS bucket are billed
- Check if 403 GET requests to a public GCS bucket non-existing file are billed
Special thanks to
- Google Cloud Champions Innovator program, for their members’ support and feedback.
- Google Cloud Innovators Plus program, for their annual free GCP credits which were used for this (and many others) project.
This article was jointly written by Marcos Manuel Ortega (LinkedIn), director at Indavelopers, and Julio Quinteros P. (@jquinterosp), Data & AI/ML manager/practice lead at Axmos