classification
Title: a python embedded program may load "C:\Lib\os.py" on windows system
Type: security Stage:
Components: Interpreter Core, Windows Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: christian.heimes, eryksun, houjingyi233, paul.moore, steve.dower, tim.golden, vstinner, zach.ware
Priority: normal Keywords:

Created on 2021-01-12 03:58 by houjingyi233, last changed 2021-01-12 17:24 by vstinner.

Messages (7)
msg384882 - (view) Author: houjingyi (houjingyi233) Date: 2021-01-12 03:58
environment: windows 10, python3.8.7 installed to "C:\Program Files\Python38".

datail info: According to https://docs.python.org/3/c-api/init.html: "Py_SetPath() set the default module search path. If this function is called before Py_Initialize(), then Py_GetPath() won’t attempt to compute a default search path but uses the one provided instead."
Write following code that only call Py_Initialize():

#include <iostream>
#include <Python.h>
#include <Windows.h>
using namespace std;
int main()
{
Py_Initialize();
} 

In visual studio add "C:\Program Files\Python38\include" to AdditionalIncludeDirectories, add "C:\Program Files\Python38\libs\python38.lib" to AdditionalDependencies to compile it to poc.exe. Copy "C:\Program Files\Python38\Lib" to "C:\Lib" and modify "C:\Lib\os.py" to execute any code we like. For example we can add "import os" and add "os.system(notepad)" in function "def _exists(name)". Now run poc.exe it will create notepad. 

impact: In my report I showed that a python embedded program may load "C:\Lib\os.py" which lower privileged user can control. If this program runs as administrator then this may cause vertical privilege escalation, low privileged user gets higher privilege; If this program do not run as administrator then this may cause vertical privilege escalation, low privileged user can execute code as others(https://en.wikipedia.org/wiki/Privilege_escalation). In either case, the access control of the windows system is broken.

notice: The report was sent to security@python.org before and they suggested it can be reported publicly.
msg384905 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021-01-12 09:59
The reference documentation for the Python Path Configuration can be found at:
https://docs.python.org/dev/c-api/init_config.html#python-path-configuration

Currently, Py_SetPath(path) indirectly sets the following PyConfig members:

* prefix = ""
* exec_prefix = ""
* module_search_paths = 'path' parameter, string split at DELIM delimiter (";" character on Windows)
* module_search_paths_set = 1

If you want to get the current Python Path Configuration, you can use:
---
import _testinternalcapi
import pprint
pprint.pprint(_testinternalcapi.get_configs())
---

Example with Python 3.10 (extract):
---
{'config': {...
            'argv': ['x.py'],
            ...
            'base_exec_prefix': '/usr/local',
            'base_executable': '/home/vstinner/python/master/python',
            'base_prefix': '/usr/local',
            ...
            'exec_prefix': '/usr/local',
            'executable': '/home/vstinner/python/master/python',
            ...
            'home': None,
            ...
            'module_search_paths': ['/usr/local/lib/python310.zip',
                                    '/home/vstinner/python/master/Lib',
                                    '/home/vstinner/python/master/build/lib.linux-x86_64-3.10-pydebug'],
            'module_search_paths_set': 1,
            ...
            'orig_argv': ['./python', 'x.py'],
            'parse_argv': 2,
            ...
            'pathconfig_warnings': 1,
            'platlibdir': 'lib',
            'prefix': '/usr/local',
            'program_name': './python',
            ...},
 ...,
 'path_config': {'exec_prefix': '/usr/local',
                 'home': None,
                 'module_search_path': '/usr/local/lib/python310.zip:/home/vstinner/python/master/Lib:/home/vstinner/python/master/build/lib.linux-x86_64-3.10-pydebug',
                 'prefix': '/usr/local',
                 'program_full_path': '/home/vstinner/python/master/python',
                 'program_name': './python'},
 ...}
---

See also bpo-42260: "[C API] Add PyInterpreterState_SetConfig(): reconfigure an interpreter" which aims to rewrite the code to configure the Python Path Configuration from C to Python.
msg384907 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021-01-12 10:04
Python has an unit test on Py_SetPath(): test_embed.test_init_setpath.

> Write following code that only call Py_Initialize(): (...)

Do you mean that on Windows Python looks for modules in C:\Lib\ just by calling Py_Initialize()? 


> According to https://docs.python.org/3/c-api/init.html: "Py_SetPath() set the default module search path.

I don't understand. Do you have your issue with and without calling Py_SetPath()? Can you please provide an example of code reproducing the issue?
msg384908 - (view) Author: Paul Moore (paul.moore) * (Python committer) Date: 2021-01-12 10:05
I thought that *all* versions of Python located the standard library by searching for os.py...
msg384909 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021-01-12 10:24
> I thought that *all* versions of Python located the standard library by searching for os.py...

The current implementation of the Python Path Configuration can be found in:

* Windows: PC/getpathp.c
* Unix: Modules/getpath.c

It should be skipped if Py_SetPath() is used, but I recommend to only rely on PyConfig and its documentation:
https://docs.python.org/dev/c-api/init_config.html#python-path-configuration

If the documentation is wrong, it must be fixed ;-)

I still don't understand if the issue involves Py_SetPath() or not.
msg384966 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2021-01-12 17:03
> I still don't understand if the issue involves Py_SetPath() or not.

It seems to me that this issue is concerned with the default behavior that looks for the "lib/os.py" landmark via search_for_prefix() in PC/getpathp.c. Starting at the image path, it searches for the landmark up to the root directory. Any authenticated user can create a "C:/Lib" directory.

That said, I don't think the normal Python distribution is intended to be a base installation for embedding applications, as opposed to using a private copy of the embedded distribution. So in practice there may be no issue here.
msg384967 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2021-01-12 17:24
Hum. The code in Python 3.8 is complicated. When Py_SetPath() is called, Python starts by computing the Python Path Configuration because of:

    /* Getting the program full path calls pathconfig_global_init() */
    wchar_t *program_full_path = _PyMem_RawWcsdup(Py_GetProgramFullPath());

The result is stored info _Py_path_config. Then Py_SetPath() overrides program_full_path, prefix, exec_prefix and module_search_path of _Py_path_config.

When Python reachs Py_Initialize(), PyConfig_Read() does not compute the Python Path Configuration, but copies what comes from _Py_path_config.

--

I changed the code a lot of Python 3.10 to make it more deterministic and simpler. For example, Py_SetPath() no longer computes the Python Path Configuration.

commit ace3f9a0ce7b9fe8ae757fdd614f1e7a171f92b0
Author: Victor Stinner <vstinner@python.org>
Date:   Tue Nov 10 21:10:22 2020 +0100

    bpo-42260: Fix _PyConfig_Read() if compute_path_config=0 (GH-23220)
    
    Fix _PyConfig_Read() if compute_path_config=0: use values set by
    Py_SetPath(), Py_SetPythonHome() and Py_SetProgramName(). Add
    compute_path_config parameter to _PyConfig_InitPathConfig().
    
    The following functions now return NULL if called before
    Py_Initialize():
    
    * Py_GetExecPrefix()
    * Py_GetPath()
    * Py_GetPrefix()
    * Py_GetProgramFullPath()
    * Py_GetProgramName()
    * Py_GetPythonHome()
    
    These functions no longer automatically computes the Python Path
    Configuration. Moreover, Py_SetPath() no longer computes
    program_full_path.

This change is a little bit backward incompatible, even I would not recommend to call Py_GetXXX() functions before Py_Initialize() in Python 3.8 or 3.9.
History
Date User Action Args
2021-01-12 17:24:20vstinnersetmessages: + msg384967
2021-01-12 17:03:00eryksunsetnosy: + eryksun
messages: + msg384966
2021-01-12 10:24:58vstinnersetmessages: + msg384909
2021-01-12 10:05:52paul.mooresetmessages: + msg384908
2021-01-12 10:04:02vstinnersetmessages: + msg384907
2021-01-12 09:59:55vstinnersetmessages: + msg384905
2021-01-12 07:05:09christian.heimessetnosy: + christian.heimes
2021-01-12 07:04:57christian.heimessetnosy: + paul.moore, tim.golden, vstinner, zach.ware, steve.dower
type: behavior -> security
components: + Interpreter Core, Windows, - C API
2021-01-12 03:58:43houjingyi233create