CVE-2019-7539
Description
A code injection vulnerability in ipycache <=2016-05-31 allows arbitrary code execution via malicious pickle deserialization.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A code injection vulnerability in ipycache <=2016-05-31 allows arbitrary code execution via malicious pickle deserialization.
Vulnerability
The load_vars function in ipycache (all versions up to and including 2016-05-31) uses Python's pickle.load without any restrictions. This allows an attacker to craft a malicious pickle file that, when loaded, can execute arbitrary code during deserialization [1][2].
Exploitation
An attacker must be able to provide a specially crafted pickle file to be loaded by load_vars. This could occur if the attacker can write to the cache file path used by ipycache, for example by controlling input to an IPython or Jupyter notebook session. No authentication is required beyond the ability to influence the file contents. The exploit involves creating a pickle containing an object with a __reduce__ method that executes arbitrary commands [3][4].
Impact
Successful exploitation leads to arbitrary code execution in the context of the user running the notebook (e.g., the IPython kernel). This can result in full compromise of the user's data and system, including privilege escalation if the user has elevated rights.
Mitigation
The vulnerability is fixed in ipycache by commits c73a726 [3] and 9cc7cb8 [4], which introduced RestrictedUnpickler to limit deserialization to only _io.StringIO. Users should update to a version including these fixes. No workaround is available; until patched, avoid loading untrusted pickle files.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ipycachePyPI | <= 0.1.4 | — |
Affected products
2Patches
2c73a726744c9Fix for arbitrary code execution while unpickling in ipycache.load_vars() method.
2 files changed · +44 −8
ipycache.py+29 −8 modified@@ -7,18 +7,20 @@ # Imports #------------------------------------------------------------------------------ +import hashlib # Stdlib -import inspect, os, sys, textwrap, re +import io +import os +import re +import sys # Our own from IPython.config.configurable import Configurable from IPython.core import magic_arguments -from IPython.core.magic import Magics, magics_class, line_magic, cell_magic -from IPython.utils.traitlets import Unicode -from IPython.utils.io import CapturedIO, capture_output +from IPython.core.magic import Magics, magics_class, cell_magic from IPython.display import clear_output -import hashlib - +from IPython.utils.io import CapturedIO +from IPython.utils.traitlets import Unicode #------------------------------------------------------------------------------ # Six utility functions for Python 2/3 compatibility @@ -115,6 +117,7 @@ def load_vars(path, vars): with open(path, 'rb') as f: # Load the variables from the cache. try: + restricted_loads(f.read()) cache = pickle.load(f) except EOFError as e: cache={} @@ -151,8 +154,26 @@ def save_vars(path, vars_d): """ with open(path, 'wb') as f: dump(vars_d, f) - - + + +# ------------------------------------------------------------------------------ +# RestrictedUnpickler - For mitigating arbitrary code execution while unpickling +# This function provides restriction of using only the io module +# ------------------------------------------------------------------------------ +class RestrictedUnpickler(pickle.Unpickler): + + def find_class(self, module, name): + if module == '_io' and name == 'StringIO': + return getattr(sys.modules[module], name) + # Forbid everything else. + raise pickle.UnpicklingError("global '%s.%s' is forbidden" % + (module, name)) + + +def restricted_loads(s): + """Helper function analogous to pickle.loads().""" + return RestrictedUnpickler(io.BytesIO(s)).load() + #------------------------------------------------------------------------------ # CapturedIO #------------------------------------------------------------------------------
test_ipycache.py+15 −0 modified@@ -206,3 +206,18 @@ def ip_push(vars): ip_user_ns=user_ns, ip_run_cell=ip_run_cell, ip_push=ip_push) os.remove(path) + + +def test_load_exploitPickle(): + class vulnLoad(): + def __init__(self): + self.a = 1 + + def __reduce__(self): + return (os.system, ('uname -a',)) + + payload = vulnLoad() + path = "malicious.pkl" + with open("malicious.pkl", "wb") as f: + pickle.dump(payload, f) + assert_raises(pickle.UnpicklingError, load_vars, path, ['a'])
9cc7cb891ff1Fix for arbitrary code execution while unpickling in ipycache.load_vars() method.
2 files changed · +36 −3
ipycache.py+21 −3 modified@@ -19,7 +19,6 @@ from IPython.display import clear_output import hashlib - #------------------------------------------------------------------------------ # Six utility functions for Python 2/3 compatibility #------------------------------------------------------------------------------ @@ -115,6 +114,7 @@ def load_vars(path, vars): with open(path, 'rb') as f: # Load the variables from the cache. try: + restricted_loads(f.read()) cache = pickle.load(f) except EOFError as e: cache={} @@ -151,8 +151,26 @@ def save_vars(path, vars_d): """ with open(path, 'wb') as f: dump(vars_d, f) - - + + +# ------------------------------------------------------------------------------ +# RestrictedUnpickler - For mitigating arbitrary code execution while unpickling +# This function provides restriction of using only the io module +# ------------------------------------------------------------------------------ +class RestrictedUnpickler(pickle.Unpickler): + + def find_class(self, module, name): + if module == '_io' and name == 'StringIO': + return getattr(sys.modules[module], name) + # Forbid everything else. + raise pickle.UnpicklingError("global '%s.%s' is forbidden" % + (module, name)) + + +def restricted_loads(s): + """Helper function analogous to pickle.loads().""" + return RestrictedUnpickler(io.BytesIO(s)).load() + #------------------------------------------------------------------------------ # CapturedIO #------------------------------------------------------------------------------
test_ipycache.py+15 −0 modified@@ -206,3 +206,18 @@ def ip_push(vars): ip_user_ns=user_ns, ip_run_cell=ip_run_cell, ip_push=ip_push) os.remove(path) + + +def test_load_exploitPickle(): + class vulnLoad(): + def __init__(self): + self.a = 1 + + def __reduce__(self): + return (os.system, ('uname -a',)) + + payload = vulnLoad() + path = "malicious.pkl" + with open("malicious.pkl", "wb") as f: + pickle.dump(payload, f) + assert_raises(pickle.UnpicklingError, load_vars, path, ['a']) \ No newline at end of file
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-gcm4-q2pg-xw89ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-7539ghsaADVISORY
- github.com/adi928/ipycache/commit/9cc7cb891ff169b3e8a6f5e84afd8238f566ad8eghsaWEB
- github.com/adi928/ipycache/commit/c73a726744c90cc2cb200b159edbaf5deddcb753ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/ipycache/PYSEC-2019-180.yamlghsaWEB
- github.com/rossant/ipycache/issues/47ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.