user


user@forge:~$ sudo -l
matching defaults entries for user on forge:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
 
user user may run the following commands on forge:
    (all : ALL) NOPASSWD: /usr/bin/python3 /opt/remote-manage.py

The user user is able to execute the /usr/bin/python3 /opt/remote-manage.py command with sudo privileges without getting prompted for password

/opt/remote-manage.py


user@forge:~$ ll /opt/remote-manage.py 
-rwxr-xr-x 1 root root 1447 May 31  2021 /opt/remote-manage.py*
 
user@forge:/opt$ cat /opt/remote-manage.py
#!/usr/bin/env python3
import socket
import random
import subprocess
import pdb
 
port = random.randint(1025, 65535)
 
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', port))
    sock.listen(1)
    print(f'Listening on localhost:{port}')
    (clientsock, addr) = sock.accept()
    clientsock.send(b'Enter the secret passsword: ')
    if clientsock.recv(1024).strip().decode() != 'secretadminpassword':
        clientsock.send(b'Wrong password!\n')
    else:
        clientsock.send(b'Welcome admin!\n')
        while True:
            clientsock.send(b'\nWhat do you wanna do: \n')
            clientsock.send(b'[1] View processes\n')
            clientsock.send(b'[2] View free memory\n')
            clientsock.send(b'[3] View listening sockets\n')
            clientsock.send(b'[4] Quit\n')
            option = int(clientsock.recv(1024).strip())
            if option == 1:
                clientsock.send(subprocess.getoutput('ps aux').encode())
            elif option == 2:
                clientsock.send(subprocess.getoutput('df').encode())
            elif option == 3:
                clientsock.send(subprocess.getoutput('ss -lnt').encode())
            elif option == 4:
                clientsock.send(b'Bye\n')
                break
except Exception as e:
    print(e)
    pdb.post_mortem(e.__traceback__)
finally:
    quit()

The /opt/remote-manage.py file appears to be a Python script that contains a small set of OS commands for remote system administration

  • The code is written in Python 3 and imports several modules such as socket, random, subprocess, and pdb.
  • The script generates a random port number and binds it to the localhost address, essentially hosting a TCP server
  • The script listens for incoming connections on the bound port.
  • Upon receiving a connection, the script prompts the client to enter a password
    • The password is hard-coded into the source code; secretadminpassword
  • If the password entered by the client matches a pre-defined secret password, the script grants access to a menu that allows the client to choose from four options:
    • View running processes
    • View free memory
    • View listening sockets
    • Quit the program
  • If the password entered by the client is incorrect, the script terminates the connection.

The most important piece is easy to miss as it takes only a few lines

pdb.post_mortem


upon receiving any exception, it prints out the error and enters into the pdb.post_mortem debugging session

when pdb.post_mortem is called, it starts a new interactive debugging session that allows examining the state of the program at the time of the exception. while all the usual debugger commands can be used to inspect variables, step through code, and so on in this mode as it is designed for such operation, it is important to note that the PDB interactive debugging session in this context is as much as interactive as a regular Python interactive mode

This means that I am able to perform any regular Python expression inside the PDB session, including executing arbitrary OS commands

Moving on to Privilege Escalation phase