Twenty five plugins for WordPress were found to be vulnerable to cross-site request forgery (CSRF) attacks.
What is a CSRF attack?
In a CSRF attack, an innocent end user is tricked by an attacker into submitting a web request that they did not intend. This may cause actions to be performed on the website that can include inadvertent client or server data leakage, change of session state, or manipulation of an end user’s account.Wikipedia
CSRF attacks and WordPress
In order to protect against such attacks, the WordPress API offers one-time use security tokens, a.k.a. nonces. Checking for a nonce validity can be done with the WordPress wp_verify_nonce
function:
if (! isset( $_REQUEST['some-nonce'] ) || ! wp_verify_nonce( $_REQUEST['some-nonce'], 'some-nonce' ) ) { exit( 'Potential CSRF attack detected.' ); }
If the nonce is missing or invalid, the request is rejected.
That looks very simple but, when mixing isset()
, !isset()
, empty()
, !empty()
with the &&
(AND) and ||
(OR) operators, it may sometimes lead to some weird results. For instance, here are three examples among the many vulnerable nonces I’ve found in WordPress themes and plugins lately:
-Example #1:
if ( isset( $_POST['some-nonce'] ) && ! wp_verify_nonce( $_POST['some-nonce'], 'some-nonce' ) ) { exit( 'Potential CSRF attack detected.' ); }
The nonce will be checked only if it exists. If it doesn’t, the attacker will bypass the security check.
-Example #2:
if (! empty( $_POST['some-nonce'] ) && ! wp_verify_nonce( $_POST['some-nonce'], 'some-nonce' ) ) { exit( 'Potential CSRF attack detected.' ); }
A bit similar to the previous code snippet, the nonce won’t be checked if it doesn’t exist or is empty.
-Example #3:
if (! isset( $_POST['some-nonce'] ) && ! wp_verify_nonce( $_POST['some-nonce'], 'some-nonce' ) ) { exit( 'Potential CSRF attack detected.' ); }
The nonce will be verified only if it doesn’t exist!
Below is the list of the vulnerable plugins that were fixed by their authors.
The impact of these vulnerabilities may vary from low to high severity and lead to, for instance, XSS (stored or reflected), settings change, configuration import and modification of data in the database, among other issues.
They are sorted in no particular order.
Vulnerable Plugins
1. Cartflows (100,000+ installations).
Vulnerable version: 1.5.15 and below.
Vulnerable nonce #1: classes/class-cartflows-importer.php lines L155:
if ( isset( $_POST['cartflows-action-nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['cartflows-action-nonce'] ) ), 'cartflows-action-nonce' ) ) { return; }
Vulnerable nonce #2: classes/class-cartflows-importer.php lines L265:
if ( isset( $_POST['cartflows-action-nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['cartflows-action-nonce'] ) ), 'cartflows-action-nonce' ) ) { return; }
In both cases, if $_POST['cartflows-action-nonce']
is not set, it won’t be checked.
2. Paid Memberships Pro (100,000+ installations).
Vulnerable version: 2.4.2 and below.
Vulnerable nonces: includes/metaboxes.php L57 and 79:
57 if (!empty($_POST['pmpro_noncename']) && !wp_verify_nonce( $_POST['pmpro_noncename'], plugin_basename(__FILE__) )) { 58 return $post_id; 59 } ... ... 78 // OK, we're authenticated: we need to find and save the data 79 if(isset($_POST['pmpro_noncename'])) 80 { ... ...
It rejects $_POST['pmpro_noncename']
if it is not empty L57 and if it is not set L79. Therefore, if $_POST['pmpro_noncename']
is equal to 0, it won’t be checked.
3. Cool Timeline (10,000 installations).
Vulnerable version: 2.0.2 and below.
Vulnerable nonce: fa-icons/fa-icons-class.php L149:
if ( isset( $_POST['fa_field_icon_nonce'] ) && ! wp_verify_nonce( $_POST['fa_field_icon_nonce'], 'fa_field_icon' ) ) { return; }
If $_POST['fa_field_icon_nonce']
is not set, it won’t be checked.
4. Custom Field Template (70,000+ installations).
Vulnerable version: 2.5.1 and below.
Vulnerable nonce: custom-field-template.php L3061:
if( isset($_REQUEST['custom-field-template-verify-key']) && !wp_verify_nonce($_REQUEST['custom-field-template-verify-key'], 'custom-field-template') ) return $id;
If $_REQUEST['custom-field-template-verify-key']
is not set, it won’t
be checked.
5. eCommerce Product Catalog Plugin (10,000+ installations).
Vulnerable version: 2.9.43 and below.
Vulnerable nonce: includes/register-product.php L301:
$pricemeta_noncename = isset( $_POST[ 'pricemeta_noncename' ] ) ? $_POST[ 'pricemeta_noncename' ] : ''; if ( !empty( $pricemeta_noncename ) && !wp_verify_nonce( $pricemeta_noncename, plugin_basename( __FILE__ ) ) ) { return $post->ID; }
If $_POST[ 'pricemeta_noncename' ]
is not set or is empty, it won’t be checked.
6. NotificationX (10,000+ installations).
Vulnerable version: 1.8.2 and below.
Vulnerable nonce: public/class-nx-public.php L273:
if( ! isset( $_POST['nonce'] ) && ! wp_verify_nonce( $_POST['nonce'], 'nx_frontend_nonce' ) ) { return; }
If $_POST['nonce']
is set, it won’t be checked.
7. Product Catalog X (1,000+ installations).
Vulnerable version: 1.5.12 and below.
Vulnerable nonce: core/includes/register-product.php L301:
$pricemeta_noncename = isset( $_POST[ 'pricemeta_noncename' ] ) ? $_POST[ 'pricemeta_noncename' ] : ''; if ( !empty( $pricemeta_noncename ) && !wp_verify_nonce( $pricemeta_noncename, plugin_basename( __FILE__ ) ) ) { return $post->ID; }
If $_POST[ 'pricemeta_noncename' ]
is not set or is empty, it won’t be checked.
8. Coupon Creator (10,000+ installations).
Vulnerable version: 3.1 and below.
Vulnerable nonce: plugin-engine/src/Pngx/Admin/Meta.php L335:
//Verify Nonce if ( isset( $_POST['pngx_nonce'] ) && ! wp_verify_nonce( $_POST['pngx_nonce'], 'pngx_save_fields' ) && ( isset( $_POST['_inline_edit'] ) && ! wp_verify_nonce( $_POST['_inline_edit'], 'inlineeditnonce' ) ) ) { return; }
If either $_POST['pngx_nonce']
or $_POST['_inline_edit']
(or both) isn’t set, it won’t be checked.
9. Radio Buttons for Taxonomies (10,000+ installations).
Vulnerable version: 2.0.5 and below.
Vulnerable nonce: inc/class.WordPress_Radio_Taxonomy.php L437:
// verify nonce if ( isset( $_POST["_radio_nonce-{$this->taxonomy}"]) && ! wp_verify_nonce( $_REQUEST["_radio_nonce-{$this->taxonomy}"], "radio_nonce-{$this->taxonomy}" ) ) return $post_id;
If $_POST["_radio_nonce-{$this->taxonomy}"]
is not set, it won’t be checked.
10. Menu Swapper (7,000+ installations).
Vulnerable version: 1.1.0.2 and below.
Vulnerable nonce: includes/meta-box.php L65:
// verify nonce if( isset( $_POST['swap_meta_box_nonce'] ) && !wp_verify_nonce( $_POST['swap_meta_box_nonce'], basename(__FILE__) ) ) return $post_id;
If $_POST['swap_meta_box_nonce']
is not set, it won’t be checked.
11. Forminator (70,000+ installations).
Vulnerable version: 1.13.4 and below.
Vulnerable nonce: /library/class-export.php L158:
if ( isset( $_POST['_forminator_nonce'] ) && ! wp_verify_nonce( $_POST['_forminator_nonce'], 'forminator_export' ) ) { $redirect = add_query_arg( array( 'err_msg' => rawurlencode( __( 'Invalid request, you are not allowed to do that action.', Forminator::DOMAIN ) ), ) ); wp_safe_redirect( $redirect ); }
If $_POST['_forminator_nonce']
is not set, it won’t be checked.
12. Coming Soon & Maintenance Mode Page (20,000+ installations).
Vulnerable version: 1.57 and below.
Vulnerable nonce #1: admin/includes/ot-meta-box-api.php L204:
/* verify nonce */ if ( isset( $_POST[ $this->meta_box['id'] . '_nonce'] ) && ! wp_verify_nonce( $_POST[ $this->meta_box['id'] . '_nonce'], $this->meta_box['id'] ) ) return $post_id;
Vulnerable nonce #2: admin/includes/class-ot-meta-box.php L210:
// Verify nonce. if ( isset( $_POST[ $this->meta_box['id'] . '_nonce' ] ) && ! wp_verify_nonce( $_POST[ $this->meta_box['id'] . '_nonce' ], $this->meta_box['id'] ) ) { // phpcs:ignore return $post_id;
In both cases, if $_POST[ $this->meta_box['id'] . '_nonce']
is not set, it won’t be check
13. Woody ad snippets (80,000+ installations).
Vulnerable version: 2.3.9 and below.
Vulnerable nonce: admin/includes/class.snippets.viewtable.php L139:
$wpnonce = WINP_Plugin::app()->request->get( '_wpnonce', '' ); if ( ( ! empty( $wpnonce ) && ! wp_verify_nonce( $wpnonce, 'wbcr_inp_snippert_' . $post_id . '_action_nonce' ) ) || ! WINP_Plugin::app()->currentUserCan() ) { wp_die( 'Permission error. You can not edit this page.' ); }
If $wpnonce
is empty or not set, it won’t be checked.
14. Feed Them Social (80,000+ installations).
Vulnerable version: 2.8.6 and below.
Vulnerable nonce: includes/feed-them-functions.php L814:
if ( isset( $_REQUEST['fts_security'], $_REQUEST['fts_time'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['fts_security'] ) ), sanitize_text_field( wp_unslash( $_REQUEST['fts_time'] ) ) . 'load-more-nonce' ) ) { exit( 'Sorry, You can\'t do that!' ); } else {
If either $_REQUEST['fts_security']
or $_REQUEST['fts_time']
(or both)
isn’t set, it won’t be checked.
15. Import / Export Customizer Settings (50,000+ installations).
Vulnerable version: 1.0.3 and below.
Vulnerable nonce: inc/classes/class-astra-import-export-loader.php L105:
// Verify correct source for the $_GET data. if ( isset( $_GET['_wpnonce'] ) && ! wp_verify_nonce( $_GET['_wpnonce'], 'astra-import-complete' ) ) { return; }
If $_GET['_wpnonce']
is not set, it won’t be checked.
16. Easy Testimonials (30,000+ installations).
Vulnerable version: 3.6.1 and below.
Vulnerable nonce: include/lib/ik-custom-post-type.php L278:
//RWG: 1.30.14 - added isset($_POST[ 'my-custom-fields_wpnonce' ]) to prevent undefined index notices on new item creation if ( isset($_POST[ 'my-custom-fields_wpnonce' ]) && !wp_verify_nonce( $_POST[ 'my-custom-fields_wpnonce' ], 'my-custom-fields' ) ) return;
If $_POST['my-custom-fields_wpnonce']
is not set, it won’t be checked.
17. RSS Aggregator by Feedzy (40,000+ installations).
Vulnerable version: 3.4.2 and below.
Vulnerable nonce #1: includes/admin/feedzy-rss-feeds-admin.php L271:
if ( empty( $_POST ) || ( isset( $_POST['feedzy_category_meta_noncename'] ) && ! wp_verify_nonce( $_POST['feedzy_category_meta_noncename'], FEEDZY_BASEFILE ) ) || ! current_user_can( 'edit_post', $post_id ) ) { return $post_id; }
If $_POST['feedzy_category_meta_noncename']
is not set, it won’t be checked.
Vulnerable nonce #2: includes/admin/feedzy-rss-feeds-import.php L340:
if ( empty( $_POST ) || get_post_type( $post_id ) !== 'feedzy_imports' || ( isset( $_POST['feedzy_import_noncename'] ) && ! wp_verify_nonce( $_POST['feedzy_import_noncename'], FEEDZY_BASEFILE ) ) || ! current_user_can( 'edit_post', $post_id ) ) { return $post_id; }
If $_POST['feedzy_import_noncename']
is not set, it won’t be checked.
18. Top 10 – Popular posts plugin for WordPress (30,000+ installations).
Vulnerable version: 2.9.4 and below.
Vulnerable nonces: includes/admin/import-export.php:
155 if ( isset( $_POST['tptn_export_nonce'] ) && ! wp_verify_nonce( sanitize_key( $_POST['tptn_export_nonce'] ), 'tptn_export_nonce' ) ) { 156 return; 157 } ... ... 238 if ( isset( $_POST['tptn_import_nonce'] ) && ! wp_verify_nonce( sanitize_key( $_POST['tptn_import_nonce'] ), 'tptn_import_nonce' ) ) { 239 return; 240 } ... ... 322 if ( isset( $_POST['tptn_export_settings_nonce'] ) && ! wp_verify_nonce( sanitize_key( $_POST['tptn_export_settings_nonce'] ), 'tptn_export_settings_nonce' ) ) { 323 return; 324 } ... ... 355 if ( isset( $_POST['tptn_import_settings_nonce'] ) && ! wp_verify_nonce( sanitize_key( $_POST['tptn_import_settings_nonce'] ), 'tptn_import_settings_nonce' ) ) { 356 return; 357 }
All four nonces will be checked only if they are set.
19. Dokan (50,000+ installations).
Vulnerable version: 3.0.8 and below.
Vulnerable nonce: includes/Dashboard/Templates/Orders.php L98:
$post_data = wp_unslash( $_POST ); if ( isset( $post_data['dokan_vendor_order_export_nonce'] ) && ! wp_verify_nonce( sanitize_text_field( $post_data['dokan_vendor_order_export_nonce'] ), 'dokan_vendor_order_export_action' ) ) { return; }
If $post_data['dokan_vendor_order_export_nonce']
is not set, it won’t be checked.
20. Lightweight Sidebar Manager (50,000+ installations).
Vulnerable version: 1.1.4 and below.
Vulnerable nonce: classes/class-bsf-sb-metabox.php L85:
if ( get_post_type() != BSF_SB_POST_TYPE || ( isset( $_POST[ BSF_SB_POST_TYPE . '-nonce' ] ) && ! wp_verify_nonce( $_POST[ BSF_SB_POST_TYPE . '-nonce' ], BSF_SB_POST_TYPE ) ) ) { return $post_id; }
If $_POST[ BSF_SB_POST_TYPE . '-nonce']
is not set, it won’t be checked.
21. WP Hotel Booking (9,000+ installations).
Vulnerable version: 1.10.1 and below.
Vulnerable nonce: includes/class-wphb-ajax.php L579:
if ( ! isset( $_POST['hotel-admin-check-room-available'] ) && ! wp_verify_nonce( sanitize_key( $_POST['hotel-admin-check-room-available'] ), 'hotel_admin_check_room_available' ) ) { $result['message'] = __( 'nonce is invalid', 'wp-hotel-booking' ); wp_send_json( $result ); }
If $_POST['hotel-admin-check-room-available']
is set, it won’t be checked.
22. WP ERP (10,000+ installations).
Vulnerable version: 1.6.3 and below.
Vulnerable nonce: modules/crm/includes/functions-customer.php L3188:
if ( isset( $_REQUEST['_wpnonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'erp_create_contact_from_user' ) ) { exit; }
If $_REQUEST['_wpnonce']
is not set, it won’t be checked.
23. Best WooCommerce Multivendor Marketplace Solution (9,000+ installations).
Vulnerable version: 3.5.7 and below.
Vulnerable nonce: classes/class-wcmp-vendor-dashboard.php L437:
// verify nonce if ($_POST['vendor_add_order_nonce'] && !wp_verify_nonce($_POST['vendor_add_order_nonce'], 'dc-vendor-add-order-comment')) return false;
24. WP Project Manager (10,000+ installations).
Vulnerable version: 2.4.0 and below.
Vulnerable nonce: core/Upgrades/Upgrade.php L180:
if ( isset( $_POST['pm_nonce'] ) && ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pm_nonce'] ) ), '_nonce' ) ) { return; }
If $_POST['pm_nonce']
is not set, it won’t be checked.
25. 10WebAnalytics (10,000+ installations).
Vulnerable version: 1.2.8 and below.
Vulnerable nonce #1: gawd_class.php L306:
if(isset($_POST['security']) && !wp_verify_nonce($_POST['security'], 'gawd_admin_page_nonce')) { $response['error']['code'] = 'wrong_nonce'; $response['error']['msg'] = 'wrong_nonce'; die(json_encode($response)); }
Vulnerable nonce #2: gawd_class.php L344:
if(isset($_POST['security']) && !wp_verify_nonce($_POST['security'], 'gawd_admin_page_nonce')) { $response['error']['code'] = 'wrong_nonce'; $response['error']['msg'] = 'wrong_nonce'; if($die == true) { die(json_encode($response)); } else { return $response; } }
In both cases, if $_POST['security']
is not set, it won’t be checked.
Timeline
All vulnerabilities were reported to the authors or the WordPress plugins team on August 24th.
Recommendations
Make sure to update to the latest version if you are running any of the above-mentioned plugins.
Related posts
More WordPress plugins and themes vulnerable to CSRF attacks.
Multiple WordPress plugins fixed CSRF vulnerabilities (part 1).
Multiple WordPress plugins fixed CSRF vulnerabilities (part 2).
Multiple WordPress plugins fixed CSRF vulnerabilities (part 3).
Multiple WordPress plugins fixed CSRF vulnerabilities (part 4).
Multiple WordPress plugins fixed CSRF vulnerabilities (part 5).
Stay informed about the latest vulnerabilities
- Running WordPress? You can get email notifications about vulnerabilities in the plugins or themes installed on your blog.
- On Twitter: @nintechnet