If Facebook’s Handling of the Security of Their WordPress Plugins Is Any Indication, They Don’t Seem Too Concerned About Security
On Monday we discussed that two of Facebook’s plugins for WordPress contained vulnerabilities due to basic security failures (and mentioned in passing that another is also insecure due to the same type of issue). There attempts to resolve the vulnerabilities continued to show a lack of concern and or understanding of security, at least when it comes to WordPress plugins. It also makes you wonder what the people running the WordPress Plugin Directory are up to since they know these plugins were vulnerable and didn’t make sure they were properly fixed.
Missing Capabilities Check
With the less popular of the vulnerable plugins, Messenger Customer Chat, which has 20,000+ installs according to WordPress, we wrote this about the issue:
That is missing both a capabilities check to limit what type of users can access it and a nonce check to prevent cross-site request forgery (CSRF).
The code related to that looked like this in the version before a partial fix on Monday:
57 58 59 60 61 | function fbmcc_update_options() { update_option( 'fbmcc_enabled', "1" ); update_option( 'fbmcc_generatedCode', sanitize_textarea_field( $_POST['fbmcc_generatedCode'] ) ); wp_die(); } |
In the new version that was changed to add the nonce check:
58 59 60 61 62 63 | function fbmcc_update_options() { check_ajax_referer( 'update_fmcc_code' ); update_option( 'fbmcc_enabled', "1" ); update_option( 'fbmcc_generatedCode', sanitize_textarea_field( $_POST['fbmcc_generatedCode'] ) ); wp_die(); } |
The capabilities check was not added, which is a common problem.
What was added would normally be enough to protect against exploitation, which is more than can be said about the other plugin.
An Example and a Referer Check is Not a Nonce
With the more popular of the vulnerable plugins, Facebook for WooCommerce, which has 200,000+ installs according to WordPress, we wrote this about the issue:
When we started looking over the plugin’s settings page what we noticed was that a nonce was not being sent with an AJAX requests from that, which is needed to prevent cross-site request forgery (CSRF). Looking at the underlying code we found that. at least, many of the AJAX accessible functions lack that. One such example is the function ajax_update_fb_option().
A key word there is an example and yet Facebook only attempted to fix the example we provided.
How they fixed it is odd.
The code related to that looked like this in the version before a change made on Tuesday:
2531 2532 2533 2534 2535 2536 2537 | function ajax_update_fb_option() { WC_Facebookcommerce_Utils::check_woo_ajax_permissions('update fb options', true); if (isset($_POST) && stripos($_POST['option'], 'fb_') === 0) { update_option(sanitize_text_field($_POST['option']), sanitize_text_field($_POST['option_value'])); } wp_die(); } |
Here is it after:
2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 | function ajax_update_fb_option() { WC_Facebookcommerce_Utils::check_ajax_referer(); WC_Facebookcommerce_Utils::check_woo_ajax_permissions('update fb options', true); $wpnonce = $_POST['_wpnonce']; if (isset($_POST) && stripos($_POST['option'], 'fb_') === 0 && wp_verify_nonce($wpnonce, 'wp_ajax_ajax_update_fb_option')) { update_option(sanitize_text_field($_POST['option']), sanitize_text_field($_POST['option_value'])); } wp_die(); } |
There is nonce check added there like in the other plugin, but there is also this check:
2533 | WC_Facebookcommerce_Utils::check_ajax_referer(); |
The function that calls is new and is this:
484 485 486 487 488 489 490 491 492 493 494 495 496 | public static function check_ajax_referer() { $http_referer = null; if (!empty($_SERVER['HTTP_REFERER'])) { $http_referer = wp_unslash($_SERVER['HTTP_REFERER']); } $wp_site = get_site_url(); $http_referer_url = parse_url($http_referer); $wp_site_url = parse_url($wp_site_url); if (!empty($http_referer_url) && !empty($wp_site_url) && $http_referer_url['host'] === $wp_site_url['host']) { return; } wp_die('FBE: bad requests!'); } |
It isn’t clear what the purpose of that is supposed to be, at first we thought it was a misguided attempt to prevent cross-site request forgery (CSRF), but then we noticed the nonce check added later to the code. We can’t figure out what it supposed to accomplish since it looks like the referer being checked can be faked in a CSRF exploit attempt. Ultimately it looks it might in fact be attempted protection against CSRF that was added as well as the nonce check, which is a red flag to us.
Cross-Site Request Forgery (CSRF)/Settings Change
Let’s now provide another example of the issue and again this is just an example, there is more similarly vulnerable code.
The plugin handles changing its settings through the function wp_ajax_ajax_save_fb_settings(), which accessible to anyone logged in to WordPress through its AJAX functionality:
191 192 | add_action('wp_ajax_ajax_save_fb_settings', array($this, 'ajax_save_fb_settings'), self::FB_PRIORITY_MID); |
That function first restricts access to the rest of the code to those with the “manage_woocommerce” capability using the function check_woo_ajax_permissions() and then start the process of handling the updating of settings:
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 | function ajax_save_fb_settings() { WC_Facebookcommerce_Utils::check_woo_ajax_permissions('save settings', true); if (isset($_REQUEST)) { if (!isset($_REQUEST['facebook_for_woocommerce'])) { // This is not a request from our plugin, // some other handler or plugin probably // wants to handle it and wp_die() after. return; } if (isset($_REQUEST['api_key']) && ctype_alnum($_REQUEST['api_key'])) { $this->settings['fb_api_key'] = $_REQUEST['api_key']; } |
What is noticeable absent though is a check for a valid nonce to prevent cross-site request forgery (CSRF), so it would be possible for an attacker to cause someone logged with the “manage_woocommerce” capability to make changes to the settings without intending it.
Proof of Concept
The following proof of concept will set the fb_api_key to “1234”, when logged in as a WordPress user with the “manage_woocommerce” capability. That can be confirmed by looking at the woocommerce_facebookcommerce_settings entry in WordPress’ _options table.
Make sure to replace “[path to WordPress]” with the location of WordPress.
http://[path to WordPress]/wp-admin/admin-ajax.php?action=ajax_save_fb_settings&facebook_for_woocommerce=test&api_key=1234
Automattic and WooThemes Get Credit or Blame
The other change made in the new version of Facebook for WooCommerce to “Add more contributors to the plugin”, so now Automattic and WooThemes are attached with an insecure plugin, which seems reasonable since what brought us across Facebook’s plugins is that Automattic through the WooCommerce is installing by default this insecure plugin.
I don’t see how that WC_Facebookcommerce_Utils::check_ajax_referer method could possibly work at all. It uses a variable $wp_site_url which isn’t defined anywhere – presumably they meant to use $wp_site?