Talos Vulnerability Report

TALOS-2017-0501

Multi-Master Replication Manager for MySQL mmm_agentd Remote Command Injection Vulnerabilities

May 7, 2018
CVE Number

CVE-2017-14474, CVE-2017-14475, CVE-2017-14476, CVE-2017-14477, CVE-2017-14478, CVE-2017-14479, CVE-2017-14480, CVE-2017-14481

Summary

Multiple exploitable remote command injection vulnerabilities exist in the MySQL Master-Master Replication Manager (MMM) mmm_agentd daemon 2.2.1. mmm_agentd commonly runs with root privileges and does not require authentication by default. A specially crafted MMM protocol message can cause a shell command injection resulting in arbitrary command execution with the privileges of the mmm_agentd process. An attacker that can initiate a TCP session with mmm_agentd can trigger these vulnerabilities.

Tested Versions

MMM 2.2.1

Product URLs

http://mysql-mmm.org/

CVSSv3 Score

9.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')

Details

MMM, the Multi-Master Replication Manager for MySQL, provides high availability to MySQL database clusters. Though superseded by more modern approaches, MMM was commonly used in high availability MySQL environments up through MySQL version 5.5. In an MMM environment, each MySQL server host runs the mmm_agentd agent. In its default configuration, mmm_agentd does not require authentication and typically runs as root because it requires sufficient privileges to reconfigure network interfaces.

mmm_agentd contains multiple remotely exploitable command injection vulnerabilities. Therefore, in many MMM environments, if an unauthenticated network attacker can make a TCP connection to the mmm_agentd process, they can run arbitrary commands as root. This vulnerability occurs because mmm_agentd includes attacker-supplied input in shell commands in multiple locations without appropriate sanitization.

For example, the MMM SET_STATUS protocol message can be used to assign a number of roles to an mmm_agentd host. Roles are specified as a comma-separated list of role_name(ip_addr) pairs (e.g. role_a(10.10.10.10),role_b(10.10.10.11)). MMM::Common::Role::from_string() in lib/Common/Role.pm uses the following regular expression to parse the role name and IP address: /(.*)\((.*)\)/. Thus, everything before the last opening parenthesis will interpreted as the role name and all remaining characters up to the last closing parenthesis will be interpreted as the role IP address. An attacker can construct malicious IP address values that will cause subsequent role handling code to invoke arbitrary commands. For example:

role_a(10.10.10.10`malicious_command`)

Malicious IP address values are subject to interpretation by the shell both in mmm_agentd and in helper applications called by mmm_agentd.

Role IP address values should be validated to ensure that only expected values are specified. However, because other data flows may allow malicious input to reach vulnerable functions, all dynamic values incorporated into shell commands should be sanitized to ensure that shell metacharacters do not introduce additional arguments or execute unintended commands.

Limitations:

  • Due to the mmm_agentd protocol format, injected commands cannot contain the following characters: ',', '|', '(', and '\n'

CVE-2017-14474 - MMM::Agent::Helpers::_execute()

The MMM::Agent::Helpers::_execute() function accepts a command and an string containing arguments for the command. It constructs a Bourne shell command line by concatenating the path to the requested command, the mmm_agentd config file, and the specified arguments. _execute() runs the resulting command using the the Perl backtick operator as follows.

# lib/Agent/Helpers.pm
162  sub _execute($$$) {
163          my $command             = shift;
164          my $params              = shift;
165          my $return_all  = shift;
166
167          my $path                = $main::agent->bin_path . "/agent/$command";
168          my $config_file         = $main::agent->config_file;
169          $params = '' unless defined($params);
170
171          DEBUG "Executing $path $params";
172          my $res = `$path $config_file $params`;

Because _execute() does not sanitize $params, any shell metacharacters present in $params will be interpreted by the shell.

There are several code paths that can cause _execute() to be called with untrusted input in the $params variable. To handle roles that have been added and removed, mmm_agentd will invoke MMM::Agent::Helpers::configure_ip($if, $ip) for each added role and MMM::Agent::Helpers::clear_ip($if, $ip) for each deleted role with $ip set to the IP address value specified for the role. Both, functions pass $ip to _execute() without sanitization.

# lib/Agent/Helpers.pm
45  sub configure_ip($$) {
46          my $if = shift;
47          my $ip = shift;
48          return _execute('configure_ip', "$if $ip");
49  }
...
60  sub clear_ip($$) {
61          my $if = shift;
62          my $ip = shift;
63          return _execute('clear_ip', "$if $ip");
64  }
65

As noted above role IP addresses can contain arbitrary content, modulo a few character restrictions, allowing an attacker to execute arbitrary shell commands.

Additionally, the GET_SYSTEM_STATUS and CLEAR_BAD_ROLES MMM protocol messages can be used to invoke MMM::Agent::Helpers::check_ip($if, $ip) on the IP address value of each role. check_ip() also passes the untrusted $ip value to _execute() without further sanitization.

# lib/Agent/Helpers.pm
29  sub check_ip($$) {
30          my $if = shift;
31          my $ip = shift;
32          return _execute('check_ip', "$if $ip");
33  }

Because input may be derived from a variety of (potentially untrusted) sources, _execute() should be modified to take an array of discrete command arguments and to either avoid shell interpretation by using execv-like functionality or quote command arguments to prevent shell interpretation.

CVE-2017-14475 - MMM::Agent::Helpers::Network::add_ip() (Linux)

As seen above, in order to configure a new IP address mmm_agentd invokes:

/path/to/agent/configure_ip  /path/to/mmm_agent.conf  $if  $ip

To add the IP address to the specified interface, the configure_ip helper command invokes MMM::Agent::Helpers::Network::add_ip(). Which runs the following command on Linux hosts:

# lib/Agent/Helpers/Network.pm
70                  $output = `/sbin/ip addr add $ip/32 dev $if`;

As a result, a malicious role IP address value that has been quoted to prevent interpretation in MMM::Agent::Helpers::_execute() will arrive to add_ip() in unquoted form allowing the execution of arbitrary commands.

CVE-2017-14476 - MMM::Agent::Helpers::Network::add_ip() (Solaris)

As seen above, in order to configure a new IP address mmm_agentd invokes:

/path/to/agent/configure_ip  /path/to/mmm_agent.conf  $if  $ip

To add the IP address to the specified interface, the configure_ip helper command invokes MMM::Agent::Helpers::Network::add_ip(). Which runs the following command on Solaris hosts:

# lib/Agent/Helpers/Network.pm
74                  $output = `/usr/sbin/ifconfig $if addif $ip`;

As a result, a malicious role IP address value that has been quoted to prevent interpretation in MMM::Agent::Helpers::_execute() will arrive to add_ip() in unquoted form allowing the execution of arbitrary commands.

CVE-2017-14477 - MMM::Agent::Helpers::Network::add_ip() (FreeBSD)

As seen above, in order to configure a new IP address mmm_agentd invokes:

/path/to/agent/configure_ip  /path/to/mmm_agent.conf  $if  $ip

To add the IP address to the specified interface, the configure_ip helper command invokes MMM::Agent::Helpers::Network::add_ip(). Which runs the following command on FreeBSD hosts:

# lib/Agent/Helpers/Network.pm
84                  $output = `/sbin/ifconfig $if inet $ip netmask 255.255.255.255 alias`;

As a result, a malicious role IP address value that has been quoted to prevent interpretation in MMM::Agent::Helpers::_execute() will arrive to add_ip() in unquoted form allowing the execution of arbitrary commands.

CVE-2017-14478 - MMM::Agent::Helpers::Network::clear_ip() (Linux)

As seen above, to remove a deleted role's IP address, mmm_agentd invokes:

/path/to/agent/clear_ip  /path/to/mmm_agent.conf  $if  $ip

To remove the IP address from the specified interface, the clear_ip helper command invokes MMM::Agent::Helpers::Network::clear_ip(). Which runs the following command on Linux hosts:

# lib/Agent/Helpers/Network.pm
106                  $output = `/sbin/ip addr del $ip/32 dev $if`;

As a result, a malicious role IP address value that has been quoted to prevent interpretation in MMM::Agent::Helpers::_execute() will arrive to clear_ip() in unquoted form allowing the execution of arbitrary commands.

CVE-2017-14479 - MMM::Agent::Helpers::Network::clear_ip() (Solaris)

As seen above, to remove a deleted role's IP address, mmm_agentd invokes:

/path/to/agent/clear_ip  /path/to/mmm_agent.conf  $if  $ip

To remove the IP address from the specified interface, the clear_ip helper command invokes MMM::Agent::Helpers::Network::clear_ip(). Which runs the following command on Solaris hosts:

# lib/Agent/Helpers/Network.pm
110                  $output = `/usr/sbin/ifconfig $if removeif $ip`;

As a result, a malicious role IP address value that has been quoted to prevent interpretation in MMM::Agent::Helpers::_execute() will arrive to clear_ip() in unquoted form allowing the execution of arbitrary commands.

CVE-2017-14480 - MMM::Agent::Helpers::Network::clear_ip() (FreeBSD)

As seen above, to remove a deleted role's IP address, mmm_agentd invokes:

/path/to/agent/clear_ip  /path/to/mmm_agent.conf  $if  $ip

To remove the IP address from the specified interface, the clear_ip helper command invokes MMM::Agent::Helpers::Network::clear_ip(). Which runs the following command on FreeBSD hosts:

# lib/Agent/Helpers/Network.pm
114                  $output = `/sbin/ifconfig $if inet $ip -alias`;

As a result, a malicious role IP address value that has been quoted to prevent interpretation in MMM::Agent::Helpers::_execute() will arrive to clear_ip() in unquoted form allowing the execution of arbitrary commands.

CVE-2017-14481 - MMM::Agent::Helpers::Network::send_arp() (Solaris)

After a new IP address has been configured successfully, the implementation of the configure_ip helper command will send gratuitous ARPs:

# lib/Agent/Helpers/Actions.pm
47  sub configure_ip($$) {
48          my $if  = shift;
49          my $ip  = shift;
50
51          if (MMM::Agent::Helpers::Network::check_ip($if, $ip)) {
52                  _exit_ok('IP address is configured');
53          }
54
55          if (!MMM::Agent::Helpers::Network::add_ip($if, $ip)) {
56                  _exit_error("Could not configure ip adress $ip on interface $if!");
57          }
58          MMM::Agent::Helpers::Network::send_arp($if, $ip);
59          _exit_ok();
60  }

# lib/Agent/Helpers/Network.pm
129  sub send_arp($$) {
130          my $if = shift;
131          my $ip = shift;
...
151          elsif ($OSNAME eq 'solaris') {
152                  # Get params for send_arp
153                  my $ipaddr = `/usr/sbin/ifconfig $if`;
154
155                  # Get broadcast address and netmask
156                  $ipaddr =~ /netmask\s*([0-9a-f]+)\s*broadcast\s*([\d\.]+)/i;
157                  my $if_bcast = $1;
158                  my $if_mask = $2;
159                  `/bin/send_arp -i 100 -r 5 -p /tmp/send_arp $if $ip auto $if_bcast $if_mask`;
160          }

send_arp() does not sanitize the value of $ip before interpolating it into shell commands on Solaris systems. While dangerous, this particular instance may not be currently exploitable because send_arp() is only called if add_ip() succeeds and add_ip() will return with failure if MMM::Agent::Helpers::Network::check_ip() cannot verify that the IP address configuration attempt succeeded. check_ip() currently attempts to match the the full text of the role IP address against the value obtained from the operating system. Thus, additional non-IP address characters in $ip will cause check_ip() to return false:

# lib/Agent/Helpers/Network.pm
32  sub check_ip($$) {
33          my $if = shift;
34          my $ip = shift;
35
36          my $output;
37          if ($OSNAME eq 'linux') {
38                  $output = `/sbin/ip addr show dev $if`;
39                  _exit_error("Could not check if ip $ip is configured on $if: $output") if ($? >> 8 == 255);
40          }
41          elsif ($OSNAME eq 'solaris') {
42                  # FIXME $if is not used here
43                  $output = `/usr/sbin/ifconfig -a | grep inet`;
44                  _exit_error("Could not check if ip $ip is configured on $if: $output") if ($? >> 8 == 255);
45          }
46          elsif ($OSNAME eq 'freebsd') {
47                  $output = `/sbin/ifconfig $if | grep inet`;
48                  _exit_error("Could not check if ip $ip is configured on $if: $output") if ($? >> 8 == 255);
49          }
50          else {
51                  _exit_error("ERROR: Unsupported platform!");
52          }
53
54          return ($output =~ /\D+$ip\D+/) ? 1 : 0;
55  }

Nevertheless, send_arp() should be fixed to sanitize its shell command arguments as well, because this behavior may change in subsequent releases.

Mitigation

The impact of these vulnerabilities can be lessened by configuring mmm_agentd to require TLS mutual authentication and by using network ACLs to prevent hosts other than legitimate mmm_mond hosts from accessing mmm_agentd.

To enable TLS mutual authentication follow the steps below (examples provided using gnutls certtool):

  1. Generate unique mmm_agentd and mmm_mond CAs for MMM. mmm_agentd and mmm_mond will accept any certificate signed by the CA that they have been configured to trust. Therefore, to prevent non-MMM nodes from connecting to mmm_agentd and to prevent malicious mmm_agentd hosts from impersonating mmm_mond, fresh separate CAs should be created to endorse mmm_agentd and mmm_mond certificates.

     # Generate mmm_agentd CA certificate
     certtool --generate-privkey --outfile agentd-ca.key
     certtool --generate-self-signed --load-privkey agentd-ca.key \
              --outfile agentd-ca.pem --template /dev/stdin <<EOT
     cn = "mmm_agentd CA"
     expiration_days = 3650
     ca
     path_len = -1
     signing_key
     encryption_key
     cert_signing_key
     organization =
     unit =
     locality =
     state =
     country =
     uid =
     EOT
    
    
     # Generate mmm\_mond CA certificate
     certtool --generate-privkey --outfile mond-ca.key
     certtool --generate-self-signed --load-privkey mond-ca.key \
              --outfile mond-ca.pem --template /dev/stdin <<EOT
     cn = "mmm_mond CA"
     expiration_days = 3650
     ca
     path_len = -1
     signing_key
     encryption_key
     cert_signing_key
     organization =
     unit =
     locality =
     state =
     country =
     uid =
     EOT
    
  2. Generate a private key and certificate for each mmm_agentd host (e.g. db1):

     # Generate mmm_agentd cert
     certtool --generate-privkey --outfile db1.key
     certtool --generate-certificate --load-privkey db1.key \
              --outfile db1.pem \
              --load-ca-certificate agentd-ca.pem \
              --load-ca-privkey agentd-ca.key \
              --template /dev/stdin <<EOT
     cn = "db1 mmm_agentd"
     expiration_days = 365
     signing_key
     encryption_key
     tls_www_client
     tls_www_server
     organization =
     unit =
     locality =
     state =
     country =
     uid =
     EOT
    
  3. Generate a private key and certificate for each mmm_mond host (e.g. mmm_mond1):

     # Generate mmm_mond cert
     certtool --generate-privkey --outfile mmm_mond1.key
     certtool --generate-certificate --load-privkey mmm_mond1.key \
              --outfile mmm_mond1.pem \
              --load-ca-certificate mond-ca.pem \
              --load-ca-privkey mond-ca.key \
              --template /dev/stdin <<EOT
     cn = "mmm_mond1"
     expiration_days = 365
     signing_key
     encryption_key
     tls_www_client
     tls_www_server
     organization =
     unit =
     locality =
     state =
     country =
     uid =
     EOT
    
  4. Configure mmm_agentd hosts to require mmm_mond clients to identify with mmm_mond CA certificates. Add a section such as the following to /etc/.../mmm_agent.conf:

     <socket>
     type ssl
     cert_file /path/to/db1.pem
     key_file  /path/to/db1.key
     ca_file   /path/to/mond-ca.pem
     </socket>
    
  5. Configure mmm_mond hosts to require mmm_agentd daemons to identify with mmm_agentd CA certificates. Add a section such as the following to /etc/.../mmm_mon.conf:

     <socket>
     type ssl
     cert_file /path/to/mmm_mond1.pem
     key_file  /path/to/mmm_mond1.key
     ca_file   /path/to/agentd-ca.pem
     </socket>
    
  6. Restart mmm_agentd and mmm_mond processes

Timeline

2017-12-07 - Initial vendor contact
2018-01-11 - 2nd vendor contact
2018-03-06 - 3rd vendor contact (90 day notice)
2018-04-17 - Vendor acknowledged
2018-05-04 - Vendor confirmed patch
2018-05-07 - Public Release

Credit

Discovered by Matthew Van Gundy of Cisco ASIG.