- Critical Thinking - Bug Bounty Podcast
- Posts
- [HackerNotes Ep. 177] Double Google RCE with VRP Legend Brutecat
[HackerNotes Ep. 177] Double Google RCE with VRP Legend Brutecat
First part of two awesome episodes with brutecat, this one about the two RCE he founds and some other bugs when he started digging Google VRP
Hacker TL;DR
Two RCEs in Google Cloud production, three months apart, both in the same Application Integration product. Combined bounty: $148,337
RCE at Google is not a shell. It is arbitrary Stubby (internal RPC) calls as a production identity, basically the SSRF Google says it does not have.
Google is mostly security by obscurity. Recon is the hardest and most valuable part, which is exactly why brutecat open-sourced req2proto and reads Google's internal infra papers cover to cover
His road into Google VRP was pure OSINT: chain channel to Gaia ID to email for any YouTube user, and abuse a no-JS Forgot Username page to leak any US phone number in under an hour

Today's Sponsor: Check out Zero Trust Cloud Access from ThreatLocker https://www.criticalthinkingpodcast.io/tl-ztca
Who's brutecat
Arvin Shivram (@brutecat) only started hacking Google in mid to late 2024, which is faintly absurd given the body of work. He comes from an OSINT background and now runs Brutecat Security, where he points the same AI tooling he uses on Google at other companies. This episode is part one of a two-part interview: the double RCE in Google Cloud production, then the bugs that got him into VRP in the first place. The $500k+ AI scanning saga is the next episode.
A Double RCE in Borg
Finding the debug API
Brutecat's AI scanner flagged a few endpoints on cloudcrmipfrontend-pa (reached through its clients6.google.com alias). The first one took a Gaia ID and returned an email, which already rhymed with his older OSINT work. But scanning the rest of the API surfaced something far better: a getProtoDefinition endpoint.
Everything at Google is protobuf. The request you send is a protobuf message. If you can leak the type of a message, this endpoint dumps the full protobuf definition for it. This is the structural problem with hacking Google, the proto-JSON and protobuf obscurity, handed to you as a service.
That sits on top of the technique brutecat is already known for: req2proto. Using the weird GSPB (JSON plus protobuf) content type, you probe an endpoint with junk payloads like an array of 1,2,3,4,5 and Google's backend leaks error messages that reconstruct the full request protobuf. The catch is it only works on APIs with that content type enabled, so the getProtoDefinition method was a more general version of the same idea.
Filter injection plus the base64 trick
One endpoint, listQuotaQueue, took a filter parameter. Filters at Google usually follow the AIP-160 standard, so brutecat tried the spec until something landed:
filter=client_id>"123"
That worked, but the response came back as an error: the backend could not convert the protobuf result to JSON. The fix was ?alt=proto to ask for raw protobuf. New problem: this endpoint runs on clients6.google.com (an alias of googleapis.com that crucially accepts cookies, which is what first-party auth needs), and clients6 refuses to return raw binary protobuf, likely some anti-XSS measure.
The unlock came from a note brutecat took watching an Ezequiel Pereira talk second by second. One throwaway header:
X-Goog-Encode-Response-If-Executable: base64
This wraps the protobuf response in base64, which clients6 is happy to serve. Decode it with protoc (using the definition from getProtoDefinition) and the workflow execution logs come out: internal task executions, Spanner-to-Salesforce syncs, the works. He reported that initial leak immediately rather than digging further into customer data.
Pro Tip: keep a notes file of every gadget you ever see in a talk or writeup.
From log leak to arbitrary Stubby
Reading the leaked discovery doc, one method jumped out: GenericStubbyTypedTaskV2. Stubby is Google's internal RPC framework. Every public Google API call is just fronting Stubby calls made by a Borg task under a production identity. If you can execute arbitrary Stubby queries, you reach internal RPCs that are never meant to be public.
As Joseph put it, this is basically the SSRF that Google insists it does not have. And brutecat's framing of RCE at Google is the key mental model: a shell on a Borg task is a sandbox and not that interesting. The real impact, and the reason Google pays so much, is the Stubby access as a prod account.
Creating the task was the wall. The createDraftWorkflow call kept returning INVALID_ARGUMENT until he copied a clientId: "default" value he had spotted in the earlier leaked execution logs. Then publishing it failed with publisher cannot be the same as the last editor, and he was stuck for a month while his original report was getting the endpoints patched out from under him.
The Discord coincidence and the rollout race
Out of nowhere in some Discord, brutecat asked another researcher if they needed any protobufs, and it turned out the guy was sitting on the exact same cloudcrmipfrontend API. They had each been stuck at different points. The other researcher knew the public Application Integration GCP product cold (this internal API is the internal version of it) and had pulled the same endpoints out of that product's JS files. brutecat knew how to create the workflow. They compared notes.
A few moving parts got them over the line:
Patched endpoints had undocumented duplicates.
getProtoDefinitionwas blocked, butworkflowsupport:getProtoDefinitionstill worked. All in the discovery docshrugged got the publish working because the fix had not finished rolling out everywhere. He was in Canada, brutecat in Singapore, so it worked for him first. He shared his
1e100.nethost (Google's DNS resolvesgoogleapis.comto region-specific IPs) and brutecat pinned Burp to it to reach a server that still accepted the requestThe publisher-equals-editor block was bypassed through a
setAclendpoint: add a separate attacker-controlled Gaia, then use it to toggle the publish request and approve your own workflowFilling the Stubby task params: the public Application Integration UI leaked the required spec when you tried to configure the task there, and they grabbed a working
serverSpec: gslb:alkali-baseplusServerStatus.GetServicesstraight out of Ezequiel Pereira's old RCE writeup
They got the call to execute, sent the report, and roughly one hour later everything stopped working because the fix finished propagating. Submitting one hour later would have meant no PoC. The writeup totals the chain at $148,337.
Round two, three months later
While improving his scanner against Google Cloud, Application Integration popped up again. This time it was a blunt IDOR: put your own /project/<number> in the path but reference someone else's UID and it just works, across every endpoint in the API. The problem was the UID is a UUIDv4, so a raw report would get downgraded for lack of proof.
The leak came from the test cases feature. ListTestCases sent a client-side workflow_id filter inside the protobuf. Strip that field and the endpoint dumped the test cases for every GCP user, full of @google.com Googlers running their own integrations.
The UUID itself was still masked (a - where the UID should be in the path), so brutecat ran an AIP-160 binary search over the same filter primitive, fixing on a known test case and using greater-than / less-than to recover the owner's full UUIDv4 in about 128 requests. Claude wrote the script and nailed it first try, though brutecat had to figure out the binary-search idea himself.
With UUIDs in hand he had IDOR across all of Application Integration. To push to RCE again, he created his own integration with an internal Stubby task type. Direct execution timed out at 120 seconds, but running it through the test case feature got real backend errors: a java.io.IOException: No space left on device from a Python task, then for the Stubby task a stack trace (pulled from the workflow execution logs) referencing EventbusStubbyCallerService.ExecuteStubbyCall with a UberMint / RpcSecurityPolicy credential error. That confirmed it was hitting Stubby under a different, more privileged product account. Google confirmed it was fully exploitable and told him to stop.
That report paid $75k, the middle of Google's three RCE tiers ($50k unprivileged prod user, $75k highly privileged, $100k full Cloud admin), and the team hinted at a further escalation they would not detail.
We do subs at $25, $10, and $5, premium subscribers get access to:
– Hackalongs: live bug bounty hacking on real programs, VODs available
– Live data streams, exploits, tools, scripts & un-redacted bug reports
Need a Pentest? We just launched CTBB Pentests!
Hack full time? Check out the Full-Time Hunter’s Guild!
How brutecat Got Into Google VRP
YouTube email leak (the first bug)
It started with OSINT obsession. A site that turned an email into a YouTube channel pointed him at the mobile-only profile card feature. He sniffed his iOS traffic through Burp and saw a Gaia ID in the protobuf. The YouTube comments backend is still Gaia-tied for historic G+ reasons, so loading comments exposed the Gaia ID of every commenter.
The deeper issue: Google has obfuscated (foggy) and unobfuscated Gaia IDs, and teams disagree on whether the obfuscated one is safe to expose. Many times it converts straight to an email. When the feature shipped to web and got patched, brutecat found another path: the livechat three-dots menu. Open it (no need to even block) and it preloads the target's Gaia ID, and you can swap in any channel ID to get any user's Gaia. The block list via the People API also listed raw Gaia IDs.
To get from Gaia to email he abused Pixel Recorder sharing (pixelrecorder-pa), whose share endpoint took an obfuscated Gaia ID and returned the email. Chain it: channel ID to Gaia ID to email for any YouTube user. The share sent a notification email that would tip off the victim, so he set the recording title to roughly 1.2 million characters (no server-side length limit), which quietly broke the email send. Reported in late 2024, paid $10k as abuse.
YouTube creator emails ($20k)
Scraping YouTube as a big-data project taught him the plumbing: hitting requests directly over gRPC, the X-Goog-FieldMask header to fetch only the fields he wanted, 1e100.net load balancing across Google hosts, and an IPv6 /64 to defeat rate limits, since limits were per-IP and a /64 hands you billions of addresses to rotate (Google later moved to escalating subnet bans).
Running req2proto on get_creator_channels revealed a hidden includeSuspended request parameter. Setting it surfaced a contentOwnerAssociation in the response, carrying a contentOwnerId. Content owners are CMS / god-mode accounts handed to enterprises: strike any channel, monetize, claim content. The Copyright Match Tool silently creates a hidden content owner (an IVP-type account) behind every monetized channel for Content ID. brutecat leaked that hidden contentOwnerId, then fed it to the Content ID API's contentOwners.list, which returned the conflictNotificationEmail. That field defaults to the channel's own account email. Chain it and you have the email of any YouTube partner. Paid $20k (normal VRP this time, not abuse).
Pro Tip: when an account is auto-created, check whether an adjacent field (like a notification email) gets populated from the main account in its default state before the user sets it. That default population is the leak.
Phone leak (any US number in an hour)
In early 2025 he noticed the Forgot Username page (/signin/usernamerecovery) worked without JavaScript. That matters because Google's anti-bot botguard proof-of-work needs JS to load its challenge, so a no-JS page is a botguard bypass waiting to happen. Forgot Username confirms whether a full name plus a phone number match an account.
He assembled the pieces: PayPal-style password-reset flows leak the last digits of a phone number and the form formatting reveals the country code, and a Looker Studio ownership transfer leaked full names (unlike Drive, Looker Studio does not require the recipient to accept ownership). That gave him a name to pin and a partial number to brute toward.
Google patched the leak mid-build, but he salvaged it: the JS flow passed a real botguard token, and reusing a single botguard token gave unlimited requests. Solve the proof-of-work once, replay forever. Result: any US phone number in under an hour, with obvious SIM-swap impact. He demoed it live to journalists, who covered it. Paid $5k under abuse, which both hosts agreed badly undervalues a Google-account-to-phone-number primitive.
Discovery docs
The People API rabbit hole led him to discovery documents, which carry comments and full parameter definitions. The old /$discovery/rest route got nuked after the Content Warehouse API leak, but it is still reachable if you think about the RPC angle. For YouTube, which blocked GET requests outright, he used X-HTTP-Method-Override to send a POST that executes as a GET and leaked the largest discovery doc Google has. That doc is where he later spotted the includeSuspended field, and it still hides plenty (the testing CTP inner tube endpoints, for one).
Resources
StubZero: $148,337 RCE in Google Cloud Production - the full double-RCE writeup with interactive HTML embeds
Leaking the email of any YouTube user for $10,000 - profile card, livechat block trick, Pixel Recorder chain
Disclosing YouTube Creator Emails for a $20k Bounty - CMS / Content ID communication-email chain
Leaking the phone number of any Google user - no-JS Forgot Username, botguard token reuse
AIP-160 filtering spec - the filter language behind both the injection and the UUID binary search
brutecat on X and Brutecat Security for the writeups and consulting
That's it for the week, keep hacking!
