Unsafe deserialization in MediaUpload.new_from_json() allows arbitrary code execution via crafted JSON
@chalmerlowe đang làm issue này rồi.
Từ ngày 29/7/2026.
Đánh giá
Issue này chưa được đánh giá.
Mô tả
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
- Ngôn ngữ chính
- Python
- Star
- 8.9k
- Fork
- 2.6k
- Merge trung bình
- 2 ngày 1 giờ
- Pull request đã merge (30 ngày)
- 16
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của googleapis/google-api-python-client
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
-
test: connection leak test test_discovery_http_is_closed is shadowed and syntactically invalid Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 92/100
-
เมตาแมส Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 10/100
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 52/100
Tất cả issue của googleapis/google-api-python-client
Issue tương tự
-
[Bug] reef-hermes tells me to resume with hermes --resume, which does not work from my shell Đang mởarea: harness bug status: needs-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Human-Agent-Society/reef#625 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 80/100
learningequality/kolibri#15351 · 2 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Name consistency Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
eellak/triplestore#65 · 1 bình luận ·