Signing URLs in GCP: Convenience vs. Security

Why `iam.serviceAccounts.signBlob` permission can cause trouble in your GCP environment

July 31, 2023 · VLADYSLAV HORODIVSKYI, LEAD SECURITY CONSULTANT


TL;DR

Google Cloud Platform (GCP) signed URLs are created with the help of service account keys, which are actually RSA private keys. To sign URLs, a developer must either download the key from GCP and sign URLs on a client side, or send data to the signBlob IAM method to sign it on the GCP side with its service account key. The advantage of the signBlob IAM method is that your developers do not need to manually manage service account keys. However, this method requires an iam.serviceAccounts.signBlob permission for your application’s service account that could lead to privilege escalation within your GCP cloud environment if the service account is compromised via SSRF, RCE, or local file read vulnerabilities.

Intro

If you've ever worked with a cloud storage service (e.g., AWS S3, or GCP Cloud Storage) you may know that creating signed URLs is one way to provide secure, temporary access to objects stored on cloud buckets.

A signed URL contains authentication information, has an expiration time, and allows users without credentials to upload and download bucket objects. Anyone who knows the URL can access the resource until the URL expiration time is reached.

Signing URLs for GCP

GCP provides several ways to sign Cloud Storage URLs:

  • Signing with HMAC authentication
  • V2 signing with service account authentication
  • V4 signing with service account authentication

Signing with HMAC authentication is used for compatibility with other cloud providers.  

V2 signing is a legacy method for signing URLs with service account authentication.

This article will concentrate on two methods to create a V4 signature, which is the recommended way to sign URLs for Cloud Storage bucket objects. Notably, these methods also work for V2 signatures.

Signing via service account key

One way to generate a V4 signature is to sign a “blob” (arbitrary bytes) with a service account key (read more about service accounts here). The key is essentially a private RSA key that can be created for your service account (see docs). Cryptographically signing data with the service account private key allows GCP to both identify and authenticate data (in our case, signed URLs) and associate it with the corresponding service account.

The service account requires the storage.buckets.get permission to list the bucket in which a file is stored. Depending on the purpose of the signed URL, the storage.objects.get or storage.objects.insert permission may also be needed.

This method is implemented in the gsutil signurl command and corresponding API methods in Cloud Storage client libraries.

Example gsutil signurl command that uses a service account key (examples for your programming language can be found in docs):

CODE: https://gist.github.com/Horlad/d5aa65169dd87c7ecc548cdf337c9055.js

Keep in mind, as stated in Google documentation, managing service account keys implies a security risk if the keys aren’t managed correctly, and the responsibility for securely managing these private keys falls directly to you. In addition, for serverless environments, such as App Engine, Cloud Functions, or Cloud Run, service account keys are an inconvenient way to sign URLs because it forces programmers to manage the key and deal with its secure storing and rotation.

This fact induces developers to seek other solutions that let them create signed URLs using service account attached to a computing instance.

Due to these factors, developers seek other easier solutions which let them create signed URLs using a service account attached to a computing instance. The instance uses the service account to interact with Google Cloud API using a temporary token that can be obtained via metadata endpoints, all without managing service account static credentials.

Signing via signBlob IAM method

The signBlob IAM method is another way to generate a V4 signature for Cloud Storage URLs (see Signed URL documentation). In this method, the data (i.e., a signed URL) is sent to an IAM service. This service signs the data on the GCP side with its own private key of a service account attached to an instance and returns the signature to assemble the signed URL.

The only prerequisite is that sufficient permissions must be given to the service account (at a minimum, accounts should have storage.objects.get and iam.serviceAccounts.signBlob permissions).

The documentation provides detailed information on implementing this method in your program. Alternatively, you can use Cloud Storage client libraries that already use this method.

The following implementation is written in Python using the Cloud Storage generate_signed_url() method (the signBlob API endpoint is used under the hood if a temporary service account token is needed):

CODE: https://gist.github.com/Horlad/7de57765dda80a8f4e30947765a9f971.js

These articles and sources explain why and how to use this method in detail:

The Danger of signBlob IAM method

The service account key and signBlob IAM methods may appear to have an identical purpose (one clear advantage of the signBlob method in GCP environment is that it lets developers sign URLs without service account key management). But there is one significant difference in the signBlob approach that may not be obvious when reading the documentation: This method allows any data to be signed on behalf of any service account.

More specifically, a service account that has an iam.serviceAccounts.signBlob permission can sign data using any service account’s email or unique project ID using the name path parameter. The referenced service account must reside within the same GCP account. As a result, service account keys can be used to manually sign an authorization JWT to request a temporary access token of a service account with higher privileges.

To exploit these conditions, a malicious user must only compromise a service account token (i.e., via SSRF, RCE, or local file read vulnerabilities) with the iam.serviceAccounts.signBlob permission. To make matters worse, the signBlob IAM method cannot be limited by IAM conditions, so there is currently no method for developers to limit what service accounts can be used to sign data.

More details on how to exploit the iam.serviceAccounts.signBlob permission and a PoC exploitation script can be found in this article about GCP privilege escalation techniques by RhinoSecurity.

Because of the way Google Cloud functions, the chance of finding a default service account with high privileges is almost 100%. Default service accounts are created automatically when you enable or use Google Cloud services, and let the service access other Google Cloud resources.

When a default service account is created, it is automatically granted the basic Editor role on your project. The role includes almost all permissions related to the corresponding project, which leaves your account at risk of privilege escalation.

Vulnerable PoC

To confirm that privilege escalation within a GCP project can result from compromising a service account with the iam.serviceAccounts.signBlob permission to sign URLs , I created a Cloud Function which is vulnerable to server-side request forgery (SSRF).

In this repository, you can find Terraform script and instructions on how to set up a vulnerable testing GCP project using the script. Then you can exploit the vulnerable project by following exploit steps in the README file.

Conclusion

Both signing via service account key and signing via signBlob IAM implies security risks in your GCP environment. Using a service account key forces developers to manage the key, including secure storing and key rotation. It makes the approach inconvenient for them, especially in serverless environments like Cloud Functions, Cloud Run, and App Engine. To avoid this inconvenience, many developers use the signBlob IAM method, which allows URLs to be signed using the service account attached to a computing instance. The method uses a temporary token as credentials which can be obtained from the environment metadata.

However, if you use the signBlob IAM method to create signed URLs, a service account would require the iam.serviceAccounts.signBlob permission. A web application vulnerable to SSRF, RCE, or local file read vulnerabilities could allow service account compromise and, as a consequence of signBlob permission, compromise of the whole Google Cloud project.

Therefore, the signBlob IAM method brings more risks to your GCP environment from a security perspective. As an alternative, you can sign URLs using HMAC credentials. The method was created for compatibility with other cloud providers’ signing mechanisms. At present, the credentials can be used with Cloud Storage only. However, developers must also securely manage the keys.

If you still find the signBlob IAM method more convenient for your application, these next steps can help you mitigate impacts of service account compromise:

References

News & Updates...

We are happy to share our methodology and security guide on how to do security reviews for Ruby on Rails applications through source code. In the article you will get an idea about the architecture and design of Ruby on Rails, present security checklist to increase the coverage for penetration testing assessments, and review how to find and exploit most of the OWASP 10 vulnerabilities.

Join us in exploring Meteor JS vulnerabilities.

XSS can be particularly devastating to Electron apps, and can result in RCE and phishing that might not be viable in a browser. Electron has features to mitigate these problems, so applications should turn them on. Even XSS that would be low-impact in the browser can result in highly effective phishing if the application’s URL allowlist is improperly designed. Attacks exploit the Electron model and the application-like presentation of Electron to gain the user’s confidence.