Cross-Site Request Forgery (CSRF) Vulnerability in Formidable Forms
The three most recent releases of the plugin Formidable Forms have all fixed security vulnerabilities, which isn’t a great sign for a plugin with 200,000+ installs. The oldest fixed a PHP object injection vulnerability, the next release fixed a persistent cross-site scripting (XSS) vulnerability, and the most recent version fixed a cross-site request forgery (CSRF)/PHP objection injection vulnerability we spotted through our proactive monitoring of changes made to plugins to try catch serious vulnerabilities as they are introduced in to plugins. The next release likely is going to fix yet another vulnerability as we noticed yet another vulnerability when we were looking into the details of the persistent XSS vulnerability having been fixed, which also seems connected to the vulnerability we previously found and disclosed.
The vulnerability in this case could allow an attacker to cause entry submissions for the plugin’s forms to be deleted without the person directly causing the deletion to intend it, which is referred to as cross-site request forgery (CSRF).
On the plugin’s Entries admin page there are links to delete individual submissions that look like this:
/wp-admin/admin.php?page=formidable-entries&frm_action=destroy&id=4&form=0&_wpnonce=618239a467
The URL parameter _wpnonce in that should be what is used to prevent CSRF as the code should check a valid value being passed for that, but by simply removing that from the URL we could see that it wasn’t checked. Looking at the underlying code things are actually worse.
The code for that admin page is defined in the file /classes/controllers/FrmEntriesController.php. In that access to the page is restricted to WordPress users with the “frm_view_entries” capability:
8 | add_submenu_page( 'formidable', 'Formidable | ' . __( 'Entries', 'formidable' ), __( 'Entries', 'formidable' ), 'frm_view_entries', 'formidable-entries', 'FrmEntriesController::route' ); |
Accessing the page causes the function route() in the file to run. In that function if the GET input “frm_action” has been set to “destroy_all” the function of the same name in the file will run:
30 31 32 33 34 35 36 37 38 | public static function route() { $action = FrmAppHelper::get_param( 'frm_action', '', 'get', 'sanitize_title' ); FrmAppHelper::include_svg(); switch ( $action ) { case 'show': case 'destroy': case 'destroy_all': return self::$action(); |
That function will delete of the submissions for a form specified by the ID coming from the GET input “form”:
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 | public static function destroy_all() { if ( ! current_user_can( 'frm_delete_entries' ) ) { $frm_settings = FrmAppHelper::get_settings(); wp_die( esc_html( $frm_settings->admin_permission ) ); } $params = FrmForm::get_admin_params(); $message = ''; $errors = array(); $form_id = (int) $params['form']; if ( $form_id ) { $entry_ids = FrmDb::get_col( 'frm_items', array( 'form_id' => $form_id ) ); $action = FrmFormAction::get_action_for_form( $form_id, 'wppost', 1 ); if ( $action ) { // This action takes a while, so only trigger it if there are posts to delete. foreach ( $entry_ids as $entry_id ) { do_action( 'frm_before_destroy_entry', $entry_id ); unset( $entry_id ); } } $results = self::delete_form_entries( $form_id ); |
There is no check to insure that a valid nonce has been provided, so CSRF can occur there.
Full Disclosure
Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that, but considering that they believe that having plugins, which have millions installs, remain in the Plugin Directory despite them knowing they are vulnerable is “appropriate action”, something is very amiss with them (which is even more reason the moderation needs to be cleaned up).
Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:
Is It Fixed?
If you are reading this post down the road the best way to find out if this vulnerability or other WordPress plugin vulnerabilities in plugins you use have been fixed is to sign up for our service, since what we uniquely do when it comes to that type of data is to test to see if vulnerabilities have really been fixed. Relying on the developer’s information, can lead you astray, as we often find that they believe they have fixed vulnerabilities, but have failed to do that.
Proof of Concept
The following proof of concept will show delete the form submission for the form with ID 1, when logged in as an Administrator.
Make sure to replace “[path to WordPress]” with the location of WordPress.
http://[path to WordPress]/wp-admin/admin.php?page=formidable-entries&frm_action=destroy_all&form=1