Execution with Unnecessary Privileges in ipython
Description
IPython (Interactive Python) is a command shell for interactive computing in multiple programming languages, originally developed for the Python programming language. Affected versions are subject to an arbitrary code execution vulnerability achieved by not properly managing cross user temporary files. This vulnerability allows one user to run code as another on the same machine. All users are advised to upgrade.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ipythonPyPI | < 5.11 | 5.11 |
ipythonPyPI | >= 6.0.0, < 7.16.3 | 7.16.3 |
ipythonPyPI | >= 7.17.0, < 7.31.1 | 7.31.1 |
ipythonPyPI | >= 8.0.0, < 8.0.1 | 8.0.1 |
Affected products
1- Range: < 5.11
Patches
45fa1e409d2dcMerge pull request from GHSA-pq7m-3gw7-gq5x
6 files changed · +75 −6
docs/source/whatsnew/version7.rst+8 −0 modified@@ -2,6 +2,14 @@ 7.x Series ============ +======= +.. _version 7.16.3: + +IPython 7.16.3 (CVE-2022-21699) +=============================== + +Fixed CVE-2022-21699, see IPython 8.0.1 release notes for informations. + .. _version 716: IPython 7.16.1, 7.16.2
IPython/core/application.py+1 −1 modified@@ -133,7 +133,7 @@ def _config_file_name_changed(self, change): config_file_paths = List(Unicode()) @default('config_file_paths') def _config_file_paths_default(self): - return [os.getcwd()] + return [] extra_config_file = Unicode( help="""Path to an extra config file to load.
IPython/core/profileapp.py+4 −3 modified@@ -181,9 +181,10 @@ def list_profile_dirs(self): profiles = list_profiles_in(os.getcwd()) if profiles: print() - print("Available profiles in current directory (%s):" % os.getcwd()) - self._print_profiles(profiles) - + print( + "Profiles from CWD have been removed for security reason, see CVE-2022-21699:" + ) + print() print("To use any of the above profiles, start IPython with:") print(" ipython --profile=<name>")
IPython/core/profiledir.py+2 −2 modified@@ -186,7 +186,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): is not found, a :class:`ProfileDirError` exception will be raised. The search path algorithm is: - 1. ``os.getcwd()`` + 1. ``os.getcwd()`` # removed for security reason. 2. ``ipython_dir`` Parameters @@ -198,7 +198,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): will be "profile_<profile>". """ dirname = u'profile_' + name - paths = [os.getcwd(), ipython_dir] + paths = [ipython_dir] for p in paths: profile_dir = os.path.join(p, dirname) if os.path.isdir(profile_dir):
IPython/__init__.py+4 −0 modified@@ -65,6 +65,10 @@ __license__ = release.license __version__ = release.version version_info = release.version_info +# list of CVEs that should have been patched in this release. +# this is informational and should not be relied upon. +__patched_cves__ = {"CVE-2022-21699"} + def embed_kernel(module=None, local_ns=None, **kwargs): """Embed and start an IPython kernel in a given scope.
IPython/tests/cve.py+56 −0 added@@ -0,0 +1,56 @@ +""" +Test that CVEs stay fixed. +""" + +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from pathlib import Path +import random +import sys +import os +import string +import subprocess +import time + +def test_cve_2022_21699(): + """ + Here we test CVE-2022-21699. + + We create a temporary directory, cd into it. + Make a profile file that should not be executed and start IPython in a subprocess, + checking for the value. + + + + """ + + dangerous_profile_dir = Path('profile_default') + + dangerous_startup_dir = dangerous_profile_dir / 'startup' + dangerous_expected = 'CVE-2022-21699-'+''.join([random.choice(string.ascii_letters) for i in range(10)]) + + with TemporaryWorkingDirectory() as t: + dangerous_startup_dir.mkdir(parents=True) + (dangerous_startup_dir/ 'foo.py').write_text(f'print("{dangerous_expected}")') + # 1 sec to make sure FS is flushed. + #time.sleep(1) + cmd = [sys.executable,'-m', 'IPython'] + env = os.environ.copy() + env['IPY_TEST_SIMPLE_PROMPT'] = '1' + + + # First we fake old behavior, making sure the profile is/was actually dangerous + p_dangerous = subprocess.Popen(cmd + [f'--profile-dir={dangerous_profile_dir}'], env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r") + assert dangerous_expected in out_dangerous.decode() + + # Now that we know it _would_ have been dangerous, we test it's not loaded + p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate(b"exit\r") + assert b'IPython' in out + assert dangerous_expected not in out.decode() + assert err == b'' + + +
67ca2b3aa903Merge pull request from GHSA-pq7m-3gw7-gq5x
6 files changed · +74 −6
docs/source/whatsnew/version7.rst+7 −0 modified@@ -2,6 +2,13 @@ 7.x Series ============ +.. _version 7.31.1: + +IPython 7.31.1 (CVE-2022-21699) +=============================== + +Fixed CVE-2022-21699, see IPython 8.0.1 release notes for informations. + .. _version 7.31:
IPython/core/application.py+1 −1 modified@@ -133,7 +133,7 @@ def _config_file_name_changed(self, change): config_file_paths = List(Unicode()) @default('config_file_paths') def _config_file_paths_default(self): - return [os.getcwd()] + return [] extra_config_file = Unicode( help="""Path to an extra config file to load.
IPython/core/profileapp.py+4 −3 modified@@ -181,9 +181,10 @@ def list_profile_dirs(self): profiles = list_profiles_in(os.getcwd()) if profiles: print() - print("Available profiles in current directory (%s):" % os.getcwd()) - self._print_profiles(profiles) - + print( + "Profiles from CWD have been removed for security reason, see CVE-2022-21699:" + ) + print() print("To use any of the above profiles, start IPython with:") print(" ipython --profile=<name>")
IPython/core/profiledir.py+2 −2 modified@@ -186,7 +186,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): is not found, a :class:`ProfileDirError` exception will be raised. The search path algorithm is: - 1. ``os.getcwd()`` + 1. ``os.getcwd()`` # removed for security reason. 2. ``ipython_dir`` Parameters @@ -198,7 +198,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): will be "profile_<profile>". """ dirname = u'profile_' + name - paths = [os.getcwd(), ipython_dir] + paths = [ipython_dir] for p in paths: profile_dir = os.path.join(p, dirname) if os.path.isdir(profile_dir):
IPython/__init__.py+4 −0 modified@@ -65,6 +65,10 @@ __license__ = release.license __version__ = release.version version_info = release.version_info +# list of CVEs that should have been patched in this release. +# this is informational and should not be relied upon. +__patched_cves__ = {"CVE-2022-21699"} + def embed_kernel(module=None, local_ns=None, **kwargs): """Embed and start an IPython kernel in a given scope.
IPython/tests/cve.py+56 −0 added@@ -0,0 +1,56 @@ +""" +Test that CVEs stay fixed. +""" + +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from pathlib import Path +import random +import sys +import os +import string +import subprocess +import time + +def test_cve_2022_21699(): + """ + Here we test CVE-2022-21699. + + We create a temporary directory, cd into it. + Make a profile file that should not be executed and start IPython in a subprocess, + checking for the value. + + + + """ + + dangerous_profile_dir = Path('profile_default') + + dangerous_startup_dir = dangerous_profile_dir / 'startup' + dangerous_expected = 'CVE-2022-21699-'+''.join([random.choice(string.ascii_letters) for i in range(10)]) + + with TemporaryWorkingDirectory() as t: + dangerous_startup_dir.mkdir(parents=True) + (dangerous_startup_dir/ 'foo.py').write_text(f'print("{dangerous_expected}")') + # 1 sec to make sure FS is flushed. + #time.sleep(1) + cmd = [sys.executable,'-m', 'IPython'] + env = os.environ.copy() + env['IPY_TEST_SIMPLE_PROMPT'] = '1' + + + # First we fake old behavior, making sure the profile is/was actually dangerous + p_dangerous = subprocess.Popen(cmd + [f'--profile-dir={dangerous_profile_dir}'], env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r") + assert dangerous_expected in out_dangerous.decode() + + # Now that we know it _would_ have been dangerous, we test it's not loaded + p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate(b"exit\r") + assert b'IPython' in out + assert dangerous_expected not in out.decode() + assert err == b'' + + +
a06ca8372732Merge pull request from GHSA-pq7m-3gw7-gq5x
6 files changed · +111 −6
docs/source/whatsnew/version8.rst+44 −0 modified@@ -2,6 +2,50 @@ 8.x Series ============ + +IPython 8.0.1 (CVE-2022-21699) +------------------------------ + +IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default +values in order to prevent potential Execution with Unnecessary Privileges. + +Almost all version of IPython looks for configuration and profiles in current +working directory. Since IPython was developed before pip and environments +existed it was used a convenient way to load code/packages in a project +dependant way. + +In 2022, it is not necessary anymore, and can lead to confusing behavior where +for example cloning a repository and starting IPython or loading a notebook from +any Jupyter-Compatible interface that has ipython set as a kernel can lead to +code execution. + + +I did not find any standard way for packaged to advertise CVEs they fix, I'm +thus trying to add a ``__patched_cves__`` attribute to the IPython module that +list the CVEs that should have been fixed. This attribute is informational only +as if a executable has a flaw, this value can always be changed by an attacker. + +.. code:: + + In [1]: import IPython + + In [2]: IPython.__patched_cves__ + Out[2]: {'CVE-2022-21699'} + + In [3]: 'CVE-2022-21699' in IPython.__patched_cves__ + Out[3]: True + +Thus starting with this version: + + - The current working directory is not searched anymore for profiles or + configurations files. + - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain + the list of fixed CVE. This is informational only. + +Further details can be read on the `GitHub Advisory <https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5x>`__ + + + IPython 8.0 -----------
IPython/core/application.py+1 −1 modified@@ -157,7 +157,7 @@ def _config_file_name_changed(self, change): config_file_paths = List(Unicode()) @default('config_file_paths') def _config_file_paths_default(self): - return [os.getcwd()] + return [] extra_config_file = Unicode( help="""Path to an extra config file to load.
IPython/core/profileapp.py+4 −3 modified@@ -181,9 +181,10 @@ def list_profile_dirs(self): profiles = list_profiles_in(os.getcwd()) if profiles: print() - print("Available profiles in current directory (%s):" % os.getcwd()) - self._print_profiles(profiles) - + print( + "Profiles from CWD have been removed for security reason, see CVE-2022-21699:" + ) + print() print("To use any of the above profiles, start IPython with:") print(" ipython --profile=<name>")
IPython/core/profiledir.py+2 −2 modified@@ -188,7 +188,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): is not found, a :class:`ProfileDirError` exception will be raised. The search path algorithm is: - 1. ``os.getcwd()`` + 1. ``os.getcwd()`` # removed for security reason. 2. ``ipython_dir`` Parameters @@ -200,7 +200,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): will be "profile_<profile>". """ dirname = u'profile_' + name - paths = [os.getcwd(), ipython_dir] + paths = [ipython_dir] for p in paths: profile_dir = os.path.join(p, dirname) if os.path.isdir(profile_dir):
IPython/__init__.py+4 −0 modified@@ -60,6 +60,10 @@ __license__ = release.license __version__ = release.version version_info = release.version_info +# list of CVEs that should have been patched in this release. +# this is informational and should not be relied upon. +__patched_cves__ = {"CVE-2022-21699"} + def embed_kernel(module=None, local_ns=None, **kwargs): """Embed and start an IPython kernel in a given scope.
IPython/tests/cve.py+56 −0 added@@ -0,0 +1,56 @@ +""" +Test that CVEs stay fixed. +""" + +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from pathlib import Path +import random +import sys +import os +import string +import subprocess +import time + +def test_cve_2022_21699(): + """ + Here we test CVE-2022-21699. + + We create a temporary directory, cd into it. + Make a profile file that should not be executed and start IPython in a subprocess, + checking for the value. + + + + """ + + dangerous_profile_dir = Path('profile_default') + + dangerous_startup_dir = dangerous_profile_dir / 'startup' + dangerous_expected = 'CVE-2022-21699-'+''.join([random.choice(string.ascii_letters) for i in range(10)]) + + with TemporaryWorkingDirectory() as t: + dangerous_startup_dir.mkdir(parents=True) + (dangerous_startup_dir/ 'foo.py').write_text(f'print("{dangerous_expected}")') + # 1 sec to make sure FS is flushed. + #time.sleep(1) + cmd = [sys.executable,'-m', 'IPython'] + env = os.environ.copy() + env['IPY_TEST_SIMPLE_PROMPT'] = '1' + + + # First we fake old behavior, making sure the profile is/was actually dangerous + p_dangerous = subprocess.Popen(cmd + [f'--profile-dir={dangerous_profile_dir}'], env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r") + assert dangerous_expected in out_dangerous.decode() + + # Now that we know it _would_ have been dangerous, we test it's not loaded + p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate(b"exit\r") + assert b'IPython' in out + assert dangerous_expected not in out.decode() + assert err == b'' + + +
46a51ed69cdfMerge pull request from GHSA-pq7m-3gw7-gq5x
6 files changed · +111 −6
docs/source/whatsnew/version8.rst+44 −0 modified@@ -2,6 +2,50 @@ 8.x Series ============ + +IPython 8.0.1 (CVE-2022-21699) +------------------------------ + +IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default +values in order to prevent potential Execution with Unnecessary Privileges. + +Almost all version of IPython looks for configuration and profiles in current +working directory. Since IPython was developed before pip and environments +existed it was used a convenient way to load code/packages in a project +dependant way. + +In 2022, it is not necessary anymore, and can lead to confusing behavior where +for example cloning a repository and starting IPython or loading a notebook from +any Jupyter-Compatible interface that has ipython set as a kernel can lead to +code execution. + + +I did not find any standard way for packaged to advertise CVEs they fix, I'm +thus trying to add a ``__patched_cves__`` attribute to the IPython module that +list the CVEs that should have been fixed. This attribute is informational only +as if a executable has a flaw, this value can always be changed by an attacker. + +.. code:: + + In [1]: import IPython + + In [2]: IPython.__patched_cves__ + Out[2]: {'CVE-2022-21699'} + + In [3]: 'CVE-2022-21699' in IPython.__patched_cves__ + Out[3]: True + +Thus starting with this version: + + - The current working directory is not searched anymore for profiles or + configurations files. + - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain + the list of fixed CVE. This is informational only. + +Further details can be read on the `GitHub Advisory <https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5x>`__ + + + IPython 8.0 -----------
IPython/core/application.py+1 −1 modified@@ -157,7 +157,7 @@ def _config_file_name_changed(self, change): config_file_paths = List(Unicode()) @default('config_file_paths') def _config_file_paths_default(self): - return [os.getcwd()] + return [] extra_config_file = Unicode( help="""Path to an extra config file to load.
IPython/core/profileapp.py+4 −3 modified@@ -181,9 +181,10 @@ def list_profile_dirs(self): profiles = list_profiles_in(os.getcwd()) if profiles: print() - print("Available profiles in current directory (%s):" % os.getcwd()) - self._print_profiles(profiles) - + print( + "Profiles from CWD have been removed for security reason, see CVE-2022-21699:" + ) + print() print("To use any of the above profiles, start IPython with:") print(" ipython --profile=<name>")
IPython/core/profiledir.py+2 −2 modified@@ -188,7 +188,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): is not found, a :class:`ProfileDirError` exception will be raised. The search path algorithm is: - 1. ``os.getcwd()`` + 1. ``os.getcwd()`` # removed for security reason. 2. ``ipython_dir`` Parameters @@ -200,7 +200,7 @@ def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None): will be "profile_<profile>". """ dirname = u'profile_' + name - paths = [os.getcwd(), ipython_dir] + paths = [ipython_dir] for p in paths: profile_dir = os.path.join(p, dirname) if os.path.isdir(profile_dir):
IPython/__init__.py+4 −0 modified@@ -60,6 +60,10 @@ __license__ = release.license __version__ = release.version version_info = release.version_info +# list of CVEs that should have been patched in this release. +# this is informational and should not be relied upon. +__patched_cves__ = {"CVE-2022-21699"} + def embed_kernel(module=None, local_ns=None, **kwargs): """Embed and start an IPython kernel in a given scope.
IPython/tests/cve.py+56 −0 added@@ -0,0 +1,56 @@ +""" +Test that CVEs stay fixed. +""" + +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from pathlib import Path +import random +import sys +import os +import string +import subprocess +import time + +def test_cve_2022_21699(): + """ + Here we test CVE-2022-21699. + + We create a temporary directory, cd into it. + Make a profile file that should not be executed and start IPython in a subprocess, + checking for the value. + + + + """ + + dangerous_profile_dir = Path('profile_default') + + dangerous_startup_dir = dangerous_profile_dir / 'startup' + dangerous_expected = 'CVE-2022-21699-'+''.join([random.choice(string.ascii_letters) for i in range(10)]) + + with TemporaryWorkingDirectory() as t: + dangerous_startup_dir.mkdir(parents=True) + (dangerous_startup_dir/ 'foo.py').write_text(f'print("{dangerous_expected}")') + # 1 sec to make sure FS is flushed. + #time.sleep(1) + cmd = [sys.executable,'-m', 'IPython'] + env = os.environ.copy() + env['IPY_TEST_SIMPLE_PROMPT'] = '1' + + + # First we fake old behavior, making sure the profile is/was actually dangerous + p_dangerous = subprocess.Popen(cmd + [f'--profile-dir={dangerous_profile_dir}'], env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r") + assert dangerous_expected in out_dangerous.decode() + + # Now that we know it _would_ have been dangerous, we test it's not loaded + p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate(b"exit\r") + assert b'IPython' in out + assert dangerous_expected not in out.decode() + assert err == b'' + + +
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
14- github.com/advisories/GHSA-pq7m-3gw7-gq5xghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/CRQRTWHYXMLDJ572VGVUZMUPEOTPM3KB/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/DZ7LVZBB4D7KVSFNEQUBEHFO3JW6D2ZK/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2022-21699ghsaADVISORY
- github.com/ipython/ipython/commit/46a51ed69cdf41b4333943d9ceeb945c4ede5668ghsax_refsource_MISCWEB
- github.com/ipython/ipython/commit/5fa1e409d2dc126c456510c16ece18e08b524e5bghsaWEB
- github.com/ipython/ipython/commit/67ca2b3aa9039438e6f80e3fccca556f26100b4dghsaWEB
- github.com/ipython/ipython/commit/a06ca837273271b4acb82c29be97c0b6d12a30eaghsaWEB
- github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5xghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/ipython/PYSEC-2022-12.yamlghsaWEB
- ipython.readthedocs.io/en/stable/whatsnew/version8.htmlghsax_refsource_MISCWEB
- lists.debian.org/debian-lts-announce/2022/01/msg00021.htmlghsamailing-listx_refsource_MLISTWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/CRQRTWHYXMLDJ572VGVUZMUPEOTPM3KBghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DZ7LVZBB4D7KVSFNEQUBEHFO3JW6D2ZKghsaWEB
News mentions
0No linked articles in our index yet.