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
, andpdb
. - 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
- The password is hard-coded into the source code;
- 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