01 Jun

Persistent Cross-Site Scripting (XSS) Vulnerability in WordPress Plugin Quiz And Survey Master

The changelog entry for the latest version of the WordPress plugin Quiz And Survey Master is:

Bug: Fixed recently discovered security issues.

In looking into if that fixed a vulnerability that we should warn customers of our service that use the plugin, we ran the previous version of the plugin through our Plugin Security Checker. That flagged the following code:

User input is being directly output, which could lead to reflected cross-site scripting (XSS). File: /quiz-master-next/php/shortcodes.php Code: 179 <meta property="og:url" content="<?php echo $sharing_page_id . '?result_id=' . $_GET['result_id']; ?>" />

While that code was insecure, in our testing it didn’t lead to a vulnerability. It was secured in the new version, though:

195
<meta property="og:url" content="<?php echo $sharing_page_id . '?result_id=' . esc_attr( $_GET['result_id'] ); ?>" />

Looking at the other changes to see if there was a vulnerability fixed, we found vulnerability that still exists in the new version.

In the file /php/classes/class-qmn-quiz-manager.php, the function get_user_ip() gets the IP address of a quiz taker or more accurately, attempts to. The problem with the code, and this a fairly common issue, is that some of the values that are used to determine that are user configurable, so a value other than an IP address can be set to it.

1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
private function get_user_ip() {
	$ip            = __( 'Not collected', 'quiz-master-next' );
	$settings      = (array) get_option( 'qmn-settings' );
	$ip_collection = '0';
	if ( isset( $settings['ip_collection'] ) ) {
		$ip_collection = $settings['ip_collection'];
	}
	if ( '1' != $ip_collection ) {
		if ( $_SERVER['REMOTE_ADDR'] ) {
			$ip = $_SERVER['REMOTE_ADDR'];
		} else {
			$ip = __( 'Unknown', 'quiz-master-next' );
		}
 
		if ( getenv( 'HTTP_CLIENT_IP' ) ) {
			$ip = getenv( 'HTTP_CLIENT_IP' );
		} elseif ( getenv( 'HTTP_X_FORWARDED_FOR' ) ) {
			$ip = getenv( 'HTTP_X_FORWARDED_FOR' );
		} elseif ( getenv( 'HTTP_X_FORWARDED' ) ) {
			$ip = getenv( 'HTTP_X_FORWARDED' );
		} elseif ( getenv( 'HTTP_FORWARDED_FOR' ) ) {
			$ip = getenv( 'HTTP_FORWARDED_FOR' );
		} elseif ( getenv( 'HTTP_FORWARDED' ) ) {
			$ip = getenv( 'HTTP_FORWARDED' );
		} elseif ( getenv( 'REMOTE_ADDR' ) ) {
			$ip = getenv( 'REMOTE_ADDR' );
		} else {
			$ip = $_SERVER['REMOTE_ADDR'];
		}
	}
	return $ip;
}

There should be code that validates that the value is an IP address, to prevent that being exploited.

When the value returned from that is used saving a quiz result, no validation or sanitization is done there either:

1170
1171
1172
1173
1174
public function submit_results( $qmn_quiz_options, $qmn_array_for_variables ) {
	global $qmn_allowed_visit;
	$result_display = '';
 
	$qmn_array_for_variables['user_ip'] = $this->get_user_ip();

As the proof of concept below shows, the value isn’t escaped when output on the plugin’s Results admin page and can lead to persistent cross-site scripting (XSS). This is a pretty serious, as this type of vulnerability is one that hackers have shown a continued interest in exploiting in WordPress plugins and this plugin has 40,000+ active installations according to wordpress.org.

WordPress Causes Full Disclosure

Because of the moderators of the WordPress Support Forum’s continued inappropriate behavior we changed from reasonably disclosing to full disclosing vulnerabilities for plugins in the WordPress Plugin Directory 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. (For plugins that are also in the ClassicPress Plugin Directory, we will follow our reasonable disclosure policy.) 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:

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.

Proof of Concept

Set the value of the HTTP header “X-FORWARDED-FOR” to:

<script>alert(document.cookie);</script>

Take a quiz.

Now when visiting the plugin’s Results admin page, /wp-admin/admin.php?page=mlw_quiz_results, any available cookies will be shown in an alert box.


Concerned About The Security of the Plugins You Use?

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