22 Mar

Social Warfare Is Still Insecure

On Monday we noted that WordPress team had missed that the plugin Easy WP SMTP still contained vulnerabilities due to code related to the code that has been widely exploited this week. We tried to notify the developer of the plugin of that through a message on the WordPress Support Forum, but the moderators blocked that, so the WordPress team has been aware of that they missed those vulnerabilities for four days and yet they haven’t done anything about that. Using the moderation of the Support Forum to hide that there are problems with WordPress’ handling of security instead of working to resolve them is exactly kind of thing that led us back in September to start full disclosing vulnerabilities until the moderation of the forum is cleaned up.

You might think that someone on the WordPress side of things would have gotten the moderation cleaned up by now, since that by itself would make things better for the WordPress community, but would also stop those full disclosures, but that still hasn’t happened. That had dire consequences yesterday as a vulnerability we found in the plugin Social Warfare through our proactive monitoring of changes made to WordPress plugins in the Plugin Directory to try to catch serious vulnerabilities was widely exploited after we disclosed it.

The plugin was pulled from the Plugin Directory after we had left a message for the developer on the Support Forum, which was also blocked by the moderators. The plugin then returned with the vulnerability removed, but the insecure code that allowed anyone to have previously accessed it still unfixed, which probably shouldn’t be surprising considering what happened with the other plugin. What is hard to understand is how that could happen since our post clearly walked through the insecure code, so it shouldn’t have been hard to understand that it was still insecure by those that should be handling the security of plugins on the WordPress side of things.

It would be great if someone on the WordPress side of things can finally step in and get the forum moderation cleaned up and clean up the Plugin Directory team, so stuff like this doesn’t happen anymore. We have repeatedly offered to help the Plugin Directory team with the problems they are causing even with the grief they have unnecessarily caused us over the years, so would be happy to help if there was decision made to finally get that team improved.

So we don’t just completely repeat ourselves, we will go back through the insecure code in Social Warfare in the opposite order we explained things in the last post.

The plugin register the function init() from the class SWP_Database_Migration to run once plugins have loaded:

38
add_action( 'plugins_loaded', array( $this, 'init' ), 100 );

That will in turn cause the function debug_parameters() to be run:

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public function init() {
 
	// Check for and migrate the settings page data.
	if ( !$this->database_is_migrated() ) {
		$this->migrate();
	}
 
	// Initialize the database for new installs.
	if ( !$this->has_3_0_0_settings() ) {
		$this->initialize_database();
	}
 
	// Check for and migrate the post meta fields.
	if ( !$this->post_meta_is_migrated() ) {
		$this->update_post_meta();
		$this->update_hidden_post_meta();
		$this->update_last_migrated();
	}
 
	$this->debug_parameters();

That makes all of the following code accessible to anyone:

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
public function debug_parameters() {
	global $post, $swp_user_options;
 
 
	// Output an array of user options if called via a debugging parameter.
	if ( true === SWP_Utility::debug('get_user_options') ) :
		$options = get_option( 'social_warfare_settings', array() );
		$options = SWP_Database_Migration::filter_options( $options );
		ksort( $options );
		echo "<pre>", var_export( $options ), "</pre>";
		wp_die();
	endif;
 
	// /**
	//  * Output text representation of array of user options if called via a debugging parameter.
	//  * Text is formatted for use with `eval`.
	//  *
	//  * @since 3.5.0 | 14 DEC 2018 | Created.
	//  */
	// if ( true === SWP_Utility::debug('get_user_options_raw') ) {
	// 	$options = get_option( 'social_warfare_settings', array() );
	// 	die(var_export('return ' . $options . ';'));
	// }
 
 
 
	if ( true === SWP_Utility::debug('get_filtered_options') ) :
		global $swp_user_options;
		echo "<pre>";
		var_export( SWP_Database_Migration::filter_options( $swp_user_options ) );
		echo "</pre>";
		wp_die();
	endif;
 
	if ( true == SWP_Utility::debug('get_post_meta') ) :
		add_action( 'template_redirect', array( $this, 'print_post_meta' ) );
	endif;
 
	/**
	 * v3.4.1 brought to our attention that the default value for
	 * post meta `swp_float_location` is 'on' instead of 'deafult'.
	 *
	 *This debug paramter has an optional paramter, `post_type`, which defaults to 'page'.
	 *
	 * @since 3.4.2
	 */
	if ( true == SWP_Utility::debug('reset_float_location') ) {
		if (!is_admin()) {
			wp_die('You do not have authorization to view this page.');
		}
		$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'page';
		$this->reset_post_meta_float_location( $post_type );
	}
 
 
	// Migrate settings page if explicitly being called via a debugging parameter.
	if ( true === SWP_Utility::debug('migrate_db') ) {
		if (!is_admin()) {
			wp_die('You do not have authorization to view this page.');
		}
		$this->migrate();
	}
 
	// Initialize database if explicitly being called via a debugging parameter.
	if ( true === SWP_Utility::debug('initialize_db') ) {
		if (!is_admin()) {
			wp_die('You do not have authorization to view this page.');
		}
		$this->initialize_db();
	}
 
	// Update post meta if explicitly being called via a debugging parameter.
	if ( true === SWP_Utility::debug('migrate_post_meta') ) {
		if (!is_admin()) {
			wp_die('You do not have authorization to view this page.');
		}
		$this->update_post_meta();
		$this->update_hidden_post_meta();
	}
 
	// Output the last_migrated status if called via a debugging parameter.
	if ( true === SWP_Utility::debug('get_last_migrated') ) {
		if (!is_admin()) {
			wp_die('You do not have authorization to view this page.');
		}
		$this->get_last_migrated( true );
	}
 
	// Update the last migrated status if called via a debugging parameter.
	if ( true === SWP_Utility::debug('update_last_migrated') ) {
		if (!is_admin()) {
			wp_die('You do not have authorization to view this page.');
		}
		$this->update_last_migrated();
	}
 
	if ( true === SWP_Utility::debug( ( 'delete_plugin_data' ) ) ) {
		$password = isset($_GET['swp_confirmation']) ? urldecode($_GET['swp_confirmation']) : '';
		$user = wp_get_current_user();
		if ( !is_admin()
		|| false == current_user_can( 'administrator' )
		|| false == wp_check_password( $password, $user->user_pass, $user->ID) ) {
			wp_die('You do not have authorization to view this page.');
		}
		global $wpdb;
 
		$query =
			"DELETE FROM {$wpdb->prefix}postmeta
			 WHERE meta_key LIKE '\_%\_shares'
			 OR meta_key LIKE 'swp\_%'";
 
		$message = '';
 
		$results = $wpdb->get_results( $query, ARRAY_N );
		if ( $results ) {
			$message .= 'Deleted plugin postmeta.<br/>';
		}
 
		$deleted = delete_option('social_warfare_settings');
		if ( $deleted ) {
			$message .= 'Deleted plugin settings.<br/>';
		}
 
		$deleted = delete_option('swp_registered_options');
		if ( $deleted ) {
			$message .= 'Deleted plugin metadata.<br/>';
		}
 
		if ( $message ) {
			$message .= 'All available Social Warfare and Social Warfare - Pro data has been deleted.';
			wp_die( $message );
		}
 
		wp_die('Sorry, there was an error processing the request. If you continue to get this message and need to delete all plugin data, please contact support at https://warfareplugins.com/submit-ticket');
	}
}

Several of those seem like they are intended to have their access restricted as there is a check of the function is_admin(), but as we noted yesterday that doesn’t do what the developer seemed to think it did:

Things start off looking bad when the code checks if is_admin(), which doesn’t indicate if someone is Administrator, but if they are accessing an admin page, which even those not logged in can do (why the WordPress developer haven’t done something about that misleading function despite the problem with it being warned about before it was introduced in the production version of WordPress over 8 years ago is beyond us)

To easily see that in fact that functionality is still accessible to anyone (save for one piece that is restricted in a way that doesn’t seem totally appropriate), below is a proof of concept that shows the export of the plugin’s setting can be done by those not logged in to WordPress. There doesn’t appear to be any sensitive information that is accessible through that.

Proof of Concept

The following proof of concept will generate a page with the plugin’s settings.

Make sure to replace “[path to WordPress]” with the location of WordPress.

http://[path to WordPress]/wp-admin/admin-post.php?swp_debug=get_user_options