Unsafe deserialization in MediaUpload.new_from_json() allows arbitrary code execution via crafted JSON
Mantenedores costumam responder em até 1 dia
@chalmerlowe já está trabalhando nisso.
Desde 29/7/2026.
Avaliação
Esta issue ainda não foi avaliada.
Descrição
Potential Unsafe Deserialization via Dynamic Import in MediaUpload.new_from_json()
Summary
MediaUpload.new_from_json() in googleapiclient/http.py performs dynamic module import and class resolution using attacker-controlled _module and _class fields from JSON input.
If an application deserializes untrusted JSON using this method — either directly or indirectly through HttpRequest.from_json() — an attacker may be able to instantiate arbitrary classes and potentially achieve code execution depending on the installed Python packages and available from_json() implementations.
This resembles a classic unsafe deserialization pattern (CWE-502: Deserialization of Untrusted Data).
Affected Code
File
googleapiclient/http.py
MediaUpload.new_from_json()
Approximate lines 408–425:
@classmethod
def new_from_json(cls, s):
"""Utility class method to instantiate a MediaUpload subclass from a JSON
representation produced by to_json().
Args:
s: string, JSON from to_json().
Returns:
An instance of the subclass of MediaUpload that was serialized with
to_json().
"""
data = json.loads(s)
# Find and call the right classmethod from_json() to restore the object.
module = data["_module"]
m = __import__(module, fromlist=module.split(".")[:-1])
kls = getattr(m, data["_class"])
from_json = getattr(kls, "from_json")
return from_json(s)
Indirect Invocation via HttpRequest.from_json()
Approximate line 1144:
@staticmethod
def from_json(s, http, postproc):
d = json.loads(s)
if d["resumable"] is not None:
d["resumable"] = MediaUpload.new_from_json(d["resumable"])
return HttpRequest(
http,
postproc,
uri=d["uri"],
...
)
Technical Root Cause
The deserialization logic trusts attacker-controlled metadata fields:
_module_class
These fields are used directly for:
- Dynamic module import via
__import__() - Arbitrary attribute lookup via
getattr() - Invocation of dynamically resolved
from_json()methods
No validation, allowlist enforcement, or subclass verification exists.
The vulnerable flow is:
data = json.loads(s)
module = data["_module"] # attacker-controlled
m = __import__(module, ...)
kls = getattr(m, data["_class"]) # attacker-controlled
from_json = getattr(kls, "from_json")
return from_json(s)
This creates a generic gadget-style unsafe deserialization primitive.
Security Impact
Potential impacts include:
- Arbitrary module import
- Arbitrary class resolution
- Invocation of unintended
from_json()methods - Unsafe object construction
- Potential arbitrary code execution depending on installed packages
The practical exploitability depends heavily on:
- available installed packages
- presence of dangerous gadget classes
- application usage patterns
- attacker ability to modify serialized state
Proof of Concept
Minimal PoC — Arbitrary Module Import
from googleapiclient.http import MediaUpload
import json
payload = json.dumps({
"_module": "os",
"_class": "path"
})
try:
MediaUpload.new_from_json(payload)
except AttributeError as e:
print(f"Module was imported, failed at: {e}")
Example Output
Module was imported, failed at: module 'posixpath' has no attribute 'from_json'
This demonstrates that attacker-controlled modules are imported successfully.
Potential Gadget-Based Exploitation
If an installed package exposes a dangerous from_json() implementation:
payload = json.dumps({
"_module": "some_installed_package.dangerous_module",
"_class": "DangerousClass",
"cmd": "..."
})
MediaUpload.new_from_json(payload)
A vulnerable gadget class could potentially:
- execute subprocesses
- perform file operations
- trigger network requests
- deserialize nested attacker-controlled content
Attack Scenario
A realistic exploitation scenario requires an application that:
- Uses
MediaUpload.to_json()orHttpRequest.to_json() - Stores serialized state somewhere attacker-modifiable
- Later restores objects using
from_json()
Potential examples:
- resumable upload persistence
- task queue serialization
- distributed workers
- Redis-backed state storage
- cached upload sessions
- database-stored request objects
Mitigating Factors
Several factors reduce practical exploitability:
- most applications do not serialize
HttpRequestobjects - the API appears primarily intended for internal/test workflows
- exploitation requires a compatible gadget class
- no direct remote attack path exists in the library itself
Severity Assessment
Proposed Classification
- CWE-502: Deserialization of Untrusted Data
Suggested Fix
Restrict deserialization to known safe classes.
Example:
_ALLOWED_MEDIA_UPLOAD_CLASSES = {
("googleapiclient.http", "MediaFileUpload"),
("googleapiclient.http", "MediaIoBaseUpload"),
("googleapiclient.http", "MediaInMemoryUpload"),
}
@classmethod
def new_from_json(cls, s):
data = json.loads(s)
module = data["_module"]
class_name = data["_class"]
if (module, class_name) not in _ALLOWED_MEDIA_UPLOAD_CLASSES:
raise ValueError(
f"Refusing to deserialize untrusted class: "
f"{module}.{class_name}"
)
m = __import__(module, fromlist=module.split(".")[:-1])
kls = getattr(m, class_name)
from_json = getattr(kls, "from_json")
return from_json(s)
Environment
- Package:
google-api-python-client - Affected file:
googleapiclient/http.py - Affected methods:
MediaUpload.new_from_json()HttpRequest.from_json()
- Python versions: all supported versions
- Linguagem predominante
- Python
- Estrelas
- 8.9k
- Forks
- 2.6k
- Merge médio
- 1d 10h
- PRs com merge (30d)
- 16
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Tem um modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de googleapis/google-api-python-client
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
googleapis/google-api-python-client#2788 ·
Mantenedores costumam responder em até 1 dia
-
test: connection leak test test_discovery_http_is_closed is shadowed and syntactically invalidAberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 75/100
googleapis/google-api-python-client#2757 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 92/100
googleapis/google-api-python-client#2755 ·
Mantenedores costumam responder em até 1 dia
-
forms for APAberta
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 20/100
googleapis/google-api-python-client#2830 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 35/100
googleapis/google-api-python-client#2825 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
Todas as issues de googleapis/google-api-python-client
Issues semelhantes
-
adr
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
kristofdegrave/homeassistant-smart-charging#1607 ·
Mantenedores costumam responder em até 1 dia
-
namespace operations
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 64/100
EclipseFdn/open-vsx.org#13665 ·
Mantenedores costumam responder em até 1 dia
-
doc good first issue help wanted
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
collective/icalendar#1865 · 2 comentários ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
canonical/opentelemetry-collector-operator#409 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 85/100
mozilla/addons-release-tests#1243 ·
Mantenedores costumam responder em até 1 dia