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.
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: AIzaSyBmtG6W8gM5Y6UxzUizxtaERwjmQZ0CCYE2. “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-dlsEach 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 serviceMethodUsing 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: 1286.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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Black & White Path
We are the beacon of the cyber world, a stepping stone on the road to security.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
