How StubZero Exposed a Google Cloud Production RCE and Earned $148,337

A researcher discovered an unauthenticated debug endpoint in Google Cloud that leaked protobuf definitions, turned it into a "req2proto as a Service", abused Stubby RPC permissions, chained several API calls to achieve full remote code execution, and received a $148,337 bug‑bounty.

Black & White Path
Black & White Path
Black & White Path
How StubZero Exposed a Google Cloud Production RCE and Earned $148,337
StubZero:Google Cloud生产环境RCE漏洞
StubZero:Google Cloud生产环境RCE漏洞

1. Starting point: information leak in a debug endpoint

The automated fuzzing tool triggered an alert on cloudcrmipfrontend-pa.googleapis.com because several suspicious endpoints returned HTTP 200. Further probing revealed the public debug endpoint /v1/integrationPlatform:getProtoDefinition, which returns protobuf definitions from the internal google3 repository, including unrelated services such as YouTube. This constitutes a massive information leak, as the request and response structures of almost every API can be enumerated.

GET /v1/integrationPlatform:getProtoDefinition?fullName=youtube.api.pfiinnertube.YoutubeApiInnertube.InnerTubeContext&isEnum=false HTTP/2
Host: cloudcrmipfrontend-pa.clients6.google.com
Cookie: <redacted>
Authorization: SAPISIDHASH <redacted>
Origin: https://console.cloud.google.com
X-Goog-Api-Key: AIzaSyBmtG6W8gM5Y6UxzUizxtaERwjmQZ0CCYE

2. “req2proto as a Service”

The researcher had previously built req2proto, a tool that infers protobuf definitions from request bodies, but it could not retrieve response definitions and required APIs that support application/json+protobuf. The newly discovered endpoint effectively provides a hosted version of this capability, exposing both request and response schemas.

3. Leaking internal workflow execution queue

Calling the endpoint without query parameters returns an INVALID_ARGUMENT error, indicating that the filter parameter follows the AIP‑160 syntax. By adding the filter client_id>"123" and requesting the alt=proto format, the response can be base64‑encoded using the header X-Goog-Encode-Response-If-Executable: base64. Decoding the protobuf reveals an internal workflow queue that synchronises data from Spanner to Salesforce.

{
  "queue_items": [
    {
      "queued_request": {
        "queued_request_id": "75a885e2-c611-43f7-b4e2-ae0d87bae789",
        "client_id": "default",
        "workflow_name": "WriteToSfdc",
        "priority": "CRITICAL",
        "received_timestamp": 1763057385562,
        "event_execution_info_id": "615cd9a9-9c0e-46ec-90df-91ee42ec9c37"
      },
      "type_url": "type.googleapis.com/enterprise.crm.datalayer.WriteToSfdcRequest",
      "sfdc_object": {
        "vector_account": {
          "id": "001Kf00000wjeK3IAI",
          "due_diligence__c": "Pending"
        }
      }
    }
  ]
}

4. Stubby RPC and Google security model

Google services communicate via the internal Stubby RPC framework (the open‑source counterpart is gRPC). Each Borglet service runs with its own identity. When a request is sent to *.googleapis.com, the front‑end forwards a Stubby call using the producer service’s identity and includes the end‑user’s Gaia ID in the security ticket. The ticket determines whether the back‑end authorises the request.

Google all services use a remote‑procedure‑call infrastructure called Stubby; the open‑source version is gRPC.

Two security contexts are illustrated:

Without authentication (anonymous)
  user = anonymous
  creds = EndUserCreds
  peer = { protocol = loas, level = strong_privacy_and_integrity, host = jxcbu6.prod.google.com, role = cloud-commerce-catalog }

With first‑party authentication (Gaia user)
  user = gaiauser/0xaa22527678
  creds = EndUserCreds
  gaiaId = 640201889743
  security_realm = campus-dls

Each Stubby service defines an RpcSecurityPolicy that lists allowed RPC methods and the required credentials (e.g., auth.creds.useProdUserEUC or auth.creds.useLOAS). Only RPCs permitted by the policy can be invoked, even if the attacker controls the producer identity.

5. Full attack chain from leak to RCE

5.1 Create workflow

Attempting to create a workflow initially returns INVALID_ARGUMENT. Adding the missing clientId (observed as "default" in the leaked queue) allows the creation of a draft workflow and returns a workflow ID.

{
  "workflow": {
    "name": "my-new-workflow-test",
    "origin": "UI",
    "clientId": "default",
    "triggerConfigs": [],
    "taskConfigs": [],
    "isNewWorkflow": true
  }
}

Publishing the workflow fails with PERMISSION_DENIED because the publisher and last editor cannot be the same user.

5.2 Discord coordination

Weeks later the researcher mentioned the proto‑leak on a Discord channel. Another researcher ("shrugged") reported seeing the GenericStubbyTypedTask vector but lacked a client_id. By exchanging the client_id: "default" the missing piece was supplied and the chain could be completed.

5.3 Bypass fix via “shadow” endpoint

After Google patched the original endpoint, many calls returned PERMISSION_DENIED. The researcher discovered 1:1 “shadow” endpoints (e.g., /v1/integrationPlatform/workflowexecution:runWorkflow) that were not yet patched. Re‑sending the same request repeatedly eventually routed to an unpatched backend, allowing the operation to succeed.

5.4 Discovery of GenericStubbyTypedTaskV2

The task name GenericStubbyTypedTask does not exist in the API schema; only IO_TEMPLATE appears. JavaScript from Application Integration reveals the real name GenericStubbyTypedTaskV2 with its own icon. Attempting to use the original task fails with a missing serverSpec field.

Three required parameters were identified:

serverSpec
serviceName
serviceMethod

Using the protobuf repository from Ezequiel Pereira and the discovery document address gslb:alkali-base, the researcher configured a task that called /ServerStatus.GetServices and received an internal service list.

{"workflow": {"workflowId": "f91833bf-eacb-43ac-8490-099fef977e19", "name": "retest-test123", "taskConfigs": [{"taskName": "GenericStubbyTypedTaskV2", "taskNumber": "1", "parameters": {"serverSpec": {"stringValue": "gslb:alkali-base"}, "serviceName": {"stringValue": "ServerStatus"}, "serviceMethod": {"stringValue": "GetServices"}}}]}}

The call returned a list of internal services such as AlkaliBaseAccountService.

5.5 Bypass publish permission check

Because publishing required a different user, the attacker updated the ACL of IP_EVENTBUS_WORKFLOWS with two obfuscated Gaia IDs, then used one account to toggle the request to publish and the second account to actually publish the workflow, completing the RCE chain.

POST /v1/integrationPlatform/workflowdeployment:toggleRequestToPublishWorkflow
{"workflowId": "f91833bf-eacb-43ac-8490-099fef977e19"}

POST /v1/integrationPlatform/workflowdeployment:publishWorkflow
{"workflowId": "f91833bf-eacb-43ac-8490-099fef977e19"}

5.6 Timeline of the first RCE

2025‑12‑01 – Initial report sent, marked P0/S0, "Nice catch!"

2026‑01‑12 – Follow‑up report with upgraded RCE PoC

2026‑01‑16 – Bug‑bounty awarded $60,000 (high‑privilege tier)

6. Second RCE three months later

6.1 Cross‑tenant IDOR

Using the same API, the attacker can supply any project ID in the URL while providing a victim’s UUID in the path. The service only checks the caller’s own project permissions, returning the victim’s resources. The UUID space (UUIDv4) is too large for brute‑force, so the attacker needs to discover a victim UUID first.

6.2 Test‑case leakage

Listing test cases via ListTestCases returns entries from all projects because the filter parameter is optional. The response includes the attacker’s own version UUID repeated in every entry, allowing the attacker to infer the structure of other projects’ resources.

6.3 Binary search to extract UUID

By fixing a known test‑case ID and performing a binary search on the workflow_id field using comparison operators ( >, <=), the attacker can reconstruct the full victim UUID with about 128 requests.

$ python extract_by_id.py --token "<redacted>" --project 273897706296 --location "us-central1" --tc-id "60413427-4d07-4c36-bce0-66cfcdd81879"
... (binary search output) ...
workflow_id: fb1dc5f3-0380-491c-af90-5a141aa02f56
Total requests: 128

6.4 Configuring internal task types

Creating a workflow that includes a PythonTask succeeds, but execution times out after the default 2‑minute limit. Attempting the same with GenericStubbyTypedTaskV2 yields an "Unknown Error". Examining the execution logs shows a CredentialsUnsupportedException indicating that UberMint verification is disabled, confirming that the task parameters are directly injected into the backend ExecuteStubbyCallRequest.

6.5 Timeline of the second RCE

2026‑03‑21 – Initial report sent, marked P1/S1

2026‑03‑23 – Updated to P0/S0, "Nice catch!"

2026‑04‑28 – Bug‑bounty awarded $75,000 (high‑privilege tier)

2026‑05‑06 – Additional disclosure of a lingering GetIntegrationVersion IDOR

2026‑05‑08 – Extra $13,337 awarded for a write‑privilege finding

7. Bug‑bounty reward structure

Tier 1 – low‑privilege production access – $50 000

Tier 2 – high‑privilege production access – $75 000

Tier 3 – Google Cloud admin privileges – $100 000

The reported vulnerability qualified for the $75 000 tier because the compromised producer identity could reach a large portion of the production environment.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ProtobufInformation SecurityRCEAPI SecurityBug BountyGoogle CloudStubby RPC
Black & White Path
Written by

Black & White Path

We are the beacon of the cyber world, a stepping stone on the road to security.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.