Key Points

  • Mitiga Research discovered a method to exfiltrate sensitive data from a highly restricted GCP environment using a misconfigured Identity-Aware Proxy (IAP).
  • In line with responsible disclosure practices, we reported the issue to Google VRP. In early June 2025, Google updated the public documentation but did not change the underlying behavior or system architecture.
  • Even in environments with no outbound connectivity and strict VPC Service Controls, an attacker with the right IAM role can leak data via preflight OPTIONS requests.
  • By deploying an App Engine with a crafted app.yaml, an attacker can embed sensitive data within the configuration and retrieve it externally, bypassing traditional network egress mechanisms.
  • This research demonstrates how even minor misconfigurations in security services like IAP can be exploited for data exfiltration, especially in cloud setups where it’s not always clear what’s trusted and what’s not.

Introduction

When people talk about “highly restricted” cloud environments, they usually mean environments with no public IPs, no outbound internet, and strict VPC Service Controls locking everything down. 

On paper, these environments seem pretty bulletproof: no way in, no way out. But in this blog, I’m going to show how a small, overlooked configuration in Google Cloud’s Identity-Aware Proxy (IAP) can quietly punch a hole through that wall. 

By abusing how IAP handles Cross-Origin Resource Sharing (CORS) preflight requests, an attacker can leak data from a locked-down VM to the outside world. No socket connections, no egress, just a cool use of browser behavior and a crafted header. The goal is to show how this behavior works and why it’s important to understand the interactions between Identity Access Management (IAM), IAP, and CORS.

Defining App Engine, IAP, and CORS 

What is an App Engine?

App Engine is a platform in Google Cloud that lets you run your code without having to manage servers. You just write your app, deploy it, and Google takes care of everything else, like scaling, load balancing, and updates. It’s perfect for web apps and APIs, especially if you want something that “just works” without needing to deal with the backend infrastructure.

Since App Engine hides most of the infrastructure, it's common for teams to hand out roles/appengine.deployer just to let someone deploy new versions for the app. But that same role also lets them control the HTTP headers served to external users of the app. So now your front-end dev who just needed to tweak some CSS can also exfiltrate data to the outside world. More on that later.

What is Identity-Aware Proxy (IAP)?

IAP is a Google Cloud service that adds an extra layer of security by controlling who can access your applications. It authenticates users through Google Cloud Identity and enforces access based on their IAM roles using role-based access control (RBAC). 

When a resource is protected by IAP, only authenticated users with the appropriate IAM roles can access it, and only through the proxy. ​IAP can be used to protect various Google Cloud services, including App Engine, Compute Engine, Cloud Run, Google Kubernetes Engine (GKE), and Cloud Functions. 

This blog focuses on how the attack works with App Engine, but the technique isn't limited to that, as other IAP-protected services such as Cloud Run are vulnerable, too.

To understand how IAP protects applications, let’s first compare what happens when a request is made to a service with and without IAP enabled:

Without IAP

  1. User sends HTTPS request directly to the App Engine instance.
  2. No authentication or authorization is enforced by default unless implemented by the app itself.
  3. The request reaches the app server without any identity verification by Google Cloud.

With IAP

  1. User sends HTTPS request and the request hits IAP.
  2. IAP checks:
    • If authenticated:
      1. Request is forwarded to authorization (3)
    • If not authenticated:
      1. Redirect to Google sign-in
      2. User authenticates via OAuth 2.0
      3. Google retrieves user identity (email, ID)
  1. IAP evaluates IAM policy for the resource
    • If authorized:
      1. Request is forwarded to App Engine
      2. App Engine handles the request as usual
    • If not authorized:
      1. 403 Forbidden returned

What is Cross-Origin Resource Sharing (CORS)?

CORS is a security feature implemented by web browsers to control how web pages can request resources from a different domain than the one that served the web page. This is crucial because, by default, browsers enforce the “Same-Origin Policy," which restricts web pages from making requests to a different domain, protocol, or port to prevent malicious behavior.​

However, modern web applications often need to request resources from different origins, such as APIs, fonts, or third-party services. CORS provides a way for servers to specify who can access their resources and how using specific HTTP headers.​

When a browser makes a cross-origin HTTP request initiated from JavaScript, it first sends an HTTP request with an Origin header to the target server. The server can then respond with appropriate CORS headers, such as Access-Control-Allow-Origin, to indicate whether the request is allowed.​

For certain types of requests, like those using custom headers or methods other than GET or POST, the browser sends a preflight request using the OPTIONS method. A preflight request is an automatic HTTP OPTIONS request sent by the browser to ask the server for permission before sending the actual request. If the server approves, it responds with the necessary headers, and the browser proceeds with the actual request. This mechanism ensures that only authorized domains can access resources, enhancing security while allowing necessary cross-origin communication.​

Additional Reading:

The Attack Flow

Where the Idea for IAP Abuse Came From

Now that we’ve covered all the background, let’s get to the point. This idea started while I was experimenting with how IAP works and protects internal resources in GCP, mainly App Engine and VM instances. At some point during testing, I started looking into how IAP handles CORS.

By default, IAP blocks unauthenticated OPTIONS requests, which are sent by browsers as “preflight” checks before cross-origin calls. So, if a browser from another origin tries to send one of these to a protected app, IAP just rejects it. That makes sense.

However, when CORS is enabled in IAP, it actually allows these preflight requests through, so the application can respond with CORS headers like Access-Control-Allow-Origin. IAP must allow unauthenticated CORS preflight (OPTIONS) requests to pass through because browsers do not include credentials in preflight requests by design, and IAP will otherwise return a 401 Unauthorized, blocking the request. Since CORS requires the preflight to reach the backend to return headers like Access-Control-Allow-Origin and Access-Control-Allow-Methods, blocking it prevents the browser from sending the actual request. It doesn’t give you access to the actual app or any protected data, but it does return a response.

It made me wonder: Could this behavior be used as an exfiltration channel? Could someone inside the environment trigger a request that leaks information through those CORS headers without ever needing to send a packet to the internet?

Testing the Theory

Using an internal virtual machine with no outbound internet access to simulate an internal attacker, I deployed an App Engine protected by IAP. The app.yaml file included a secret, encoded in Base64, placed in the Access-Control-Allow-Origin header. My goal was to access that secret externally, without authentication. Since IAP is supposed to protect this App Engine, it shouldn't work.

 The file contains a secret, encoded in Base64, in the Access-Control-Allow-Origin header.

I kept the default IAP CORS setting on the App Engine disabled.

Representing the external attacker, I used a VM outside the organization with no access to internal GCP resources, no credentials, and no authentication session. I tried to access the App Engine, and, as expected, was redirected to Google authentication. 

Even when I send an HTTP OPTIONS request instead of GET, I still get an error because the CORS setting is disabled, so authentication is required for HTTP OPTIONS requests.

Sending an OPTIONS request instead of GET, I still get an error.

I also tried to access an image that I knew was hosted on another website and required the App Engine to use CORS to access it. Since the CORS setting in IAP wasn’t enabled, I received a 401 error with nothing useful in the response.

 I also encountered a 401 error when trying to use CORS to access an image hosted on another website.

So, I enabled the CORS setting in IAP.

When sending a GET request, we still get redirected to Google authentication.

However, sending an OPTIONS request instead of GET now returns 200 OK. Exactly what we expected.

sending an OPTIONS request instead of GET now returns 200 OK.

But I still didn’t receive any sensitive information from the App Engine. Finally, I tried accessing the image mentioned earlier using an OPTIONS request, and voilà!

Trying to access that image hosted on another website using the OPTIONS request, we no longer get an error.

I was able to see the Access-Control-Allow-Origin value, which is controlled by the App Engine admin. In this case, I injected a dummy Base64 string.

What You Need for This to Work

Now that we’ve walked through the attack, let’s break down the exact conditions that make it possible. This includes the required IAM roles, network access, and specific IAP configurations that an attacker needs to abuse this vector:

  • IAP protected App Engine with CORS setting enabled
  • An internal VM that can access *.googleapis.com (for the App Engine deployment)
  • An Identity with the Role roles/appengine.deployer

Putting It All Together

An attacker with access to a GCP VM that has no outbound internet connectivity and no access to other internal resources can still exfiltrate data. They can:

  1. Encode a secret they want to exfiltrate as Base64 string.
  2. Enumerate App Engine versions by executing: gcloud app versions describe.
  3. Createsa new app.yaml file and sets the Access-Control-Allow-Origin value to the Base64 string.
  4. Deploy the new App Engine file by executing gcloud app deploy.
    • At this point, the exfiltrated string is exposed and can be retrieved externally via a preflight OPTIONS request.
  5. Send OPTIONS preflight request (from somewhere outside the organization).:
  6. Execute curl -X OPTIONS -I https://domain.example.com/image.jpg
  7. Extract the exfiltrated string from the header access-control-allow-origin of the HTTP response, and decodes it.

Limitations

The exfiltration channel is bounded by two platform limits. First, any single HTTP response-header field on App Engine must not exceed 8 KB; going larger triggers a 502 (“upstream sent too big header”) and the data never leaves the project. Second, every call to gcloud app deploy consumes both Cloud Build minutes and counts toward the 10,000-deploys-per-day cap per application. As a result, large files require chunking and rate limiting to avoid quota exhaustion.

Discovery and Disclosure Timeline

The timeline for this discovery and disclosure to Google is as follows:

  • March 31, 2025: Initial vulnerability report submitted to Google Cloud VRP, describing a data exfiltration technique using IAP CORS misconfiguration.
  • April 1, 2025: Google acknowledges receipt, escalates the report to the Cloud VRP queue, and requests coordinated disclosure.
  • April 2, 2025: Google successfully reproduces the issue and files a bug with the product team and confirms the issue is under review.
  • June 4, 2025: Google resolves the issue by updating the IAP documentation to clarify that preflight OPTIONS requests are unauthenticated and unaudited by IAP. The underlying service behavior and architecture remain unchanged, but the documentation update helps clarify and mitigate potential misuse of this mechanism.

Impact of this Exfiltration Method

An attacker can run a script on a private Google Cloud Platform (GCP) VM with no public IP and no internet access and still leak internal secrets, as long as the VM's identity has the right permissions and the CORS setting is enabled. That could include access tokens from users who connected to the VM, data from buckets the identity can reach, and more.

It’s fairly simple: if the VM has permissions to deploy an App Engine, an attacker can deploy any code they want and access it over the internet. 

But it is not that simple when IAP is enabled. 

IAP is supposed to act as an identity firewall, only allowing authenticated users to access the app. So, even if the attacker deploys a malicious app, they can’t actually access it from the outside without authentication and authorization.

The problem begins when a security engineer enables CORS in the IAP settings, often without realizing that this can open the door to data exfiltration due to the way CORS works.

App Engine is not the only victim

The same access_settings.cors_settings.allow_http_options boolean exists for Cloud Run services and for IAP-protected external HTTP(S) load-balancer backends. If it is set to true there, unauthenticated OPTIONS traffic is forwarded in exactly the same way. So, this attack isn’t limited to App Engine. An attacker can also exploit other services that are protected by IAP

Final anecdote to finish

Even without a fully misconfigured app, just knowing that allow_http_options=true can let an attacker do some reconnaissance. For instance, the attacker can send an OPTIONS request to various endpoints of the IAP-protected API (without credentials). If the endpoint exists and IAP is allowing OPTIONS through, the backend might respond with something (allowed methods, or a 200 vs 404). This can tell the attacker which URLs are valid on the app, even though they can’t access the actual data. As long as the app is properly configured and secured by IAP it isn’t a major risk, but it is still an information leak. 

Automating the Exfiltration Channel

Because ChatGPT exists, you don’t need me to generate these scripts for you. You can do it yourself. But here’s the pseudocode of what they do. 

The first (upload_via_appengine) runs from inside a compromised GCP VM with the right permissions and encodes arbitrary data into chunks embedded in App Engine CORS headers. 

The second (download_from_cors) runs externally and collects those chunks by issuing unauthenticated CORS preflight requests. 

Together, they turn an IAP-protected App Engine into a reliable exfiltration mechanism, without requiring outbound internet or traditional data transfer methods.

upload_via_appengine (Internal Attacker - Authenticated Shell)

  1. Encodes a file to Base64
  2. Splits it to 400-character chunks
  3. For each chunk:
    • Modifies app.yaml and sets the allowed CORS domain to the “chunk”
    • Deploys App Engine
    • Waits 5 seconds

download_from_cors (External Attacker - Unauthenticated Shell)

  1. Uses curl -I -X OPTIONS to read CORS header
  2. Collects all chunks
  3. Decodes and writes the file

Using scripts like these, I managed to exfiltrate a 10MB file from a locked-down environment, at a blazing speed (😂) of 770kb per day.

Security Recommendations

  • Lock down IAP custom setting: Leave access_settings.cors_settings.allow_http_options at its default false to stop unauthenticated CORS preflight traffic.
  • Continuous monitoring & detection:  Export Cloud Audit Logs for IAP and create log-based detections for spikes in app engine deploy operations.
  • Build-time guard-rails: Add a static policy scan in CI (e.g., Open Policy Agent) to reject app.yaml files that set Access-Control-Allow-Origin to untrusted domains or encode unexpected strings.
  • Restrict deploy permissions to trusted identities: Limit roles/appengine.deployer to approved CI systems or service accounts only, preventing unauthorized users from deploying App Engine versions.

Summary

Even in highly locked-down GCP environments, without public IPs, outbound internet, or open firewalls, misconfigurations can open subtle yet powerful backdoors. In this research, we demonstrated how enabling CORS on an IAP-protected App Engine can be abused by an attacker with the right IAM roles to exfiltrate sensitive data using only CORS preflight OPTIONS requests.

By embedding data into the Access-Control-Allow-Origin header of an App Engine deployment and retrieving it externally via unauthenticated CORS requests, an attacker can turn a private environment into a stealthy exfiltration channel. This bypasses traditional egress controls and leverages Google-managed infrastructure as the exfiltration bridge.

This vector underscores the importance of least privilege, careful IAM role assignment, and a deep understanding of how features like IAP and CORS interact. Small configuration choices in the cloud can quietly open the door to serious risks.

Interested in more real-world cloud attack research?

Check out other findings from Mitiga Labs, including ransomware detection, stealthy SaaS abuse, and more.

LAST UPDATED:

August 27, 2025

Don't miss these stories:

From Rogue OAuth App to Cloud Infrastructure Takeover

How a rogue OAuth app led to a full AWS environment takeover. And the key steps security leaders can take to prevent similar cloud breaches.

Defending SaaS & Cloud Workflows: Supply Chain Security Insights with Idan Cohen

From GitHub Actions to SaaS platforms, supply chain threats are growing. Hear Mitiga’s Idan Cohen and Field CISO Brian Contos explore real-world compromises, detection tips, and strategies to strengthen your cloud security.

Inside Mitiga’s Forensic Data Lake: Built for Real-World Cloud Investigations

Most security tools weren’t designed for the scale or complexity of cloud investigations. Mitiga’s Forensic Data Lake was.

Measurements That Matter: What 80% MITRE Cloud ATT&CK Coverage Looks Like

Security vendors often promote “100% MITRE ATT&CK coverage.” The reality is most of those claims reflect endpoint-centric testing, not the attack surfaces organizations rely on most today: Cloud, SaaS, AI, and Identity.

How Threat Actors Used Salesforce Data Loader for Covert API Exfiltration

In recent weeks, a sophisticated threat group has targeted companies using Salesforce’s SaaS platform with a campaign focused on abusing legitimate tools for illicit data theft. Mitiga’s Threat Hunting & Incident Response team, part of Mitiga Labs, investigated one such case and discovered that a compromised Salesforce account was used in conjunction with a “Salesforce Data Loader” application, a legitimate bulk data tool, to facilitate large-scale data exfiltration of sensitive customer data.

Why Visibility Drives Everything in Modern Cybersecurity with Sevco’s Greg Fitzgerald

In this episode of Mitiga Mic, Brian Contos sits down with Greg Fitzgerald, co-founder of Sevco Security, for a candid conversation on the real state of asset visibility, prioritization, and the evolving challenges facing security teams. With nearly three decades in the industry, Fitzgerald brings perspective on how cybersecurity has shifted from endpoint tools to orchestration-wide awareness. And why that shift is critical for cloud, SaaS, AI, and identity defense. Watch the episode or read the full transcript below.