Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

Unsafe deserialization in MediaUpload.new_from_json() allows arbitrary code execution via crafted JSON

Ouverte
#2,748 0 commentaires 0 réactions 1 personne assignée Voir sur GitHub

@chalmerlowe y travaille déjà.

Depuis le 29/7/2026.

Évaluation

Cette issue n'a pas encore été évaluée.

Description

priority: p2 type: bug

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:

  1. Dynamic module import via __import__()
  2. Arbitrary attribute lookup via getattr()
  3. 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:

  1. Uses MediaUpload.to_json() or HttpRequest.to_json()
  2. Stores serialized state somewhere attacker-modifiable
  3. 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 HttpRequest objects
  • 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
Langage dominant
Python
Étoiles
8.9k
Forks
2.6k
Merge moyen
2 j 1 h
PR mergées (30 j)
16

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de googleapis/google-api-python-client

Toutes les issues de googleapis/google-api-python-client

Issues similaires

Plus d'issues Python

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.