
Visual Email Designer for WooCommerce < 1.7.2 - Multiple Author+ SQLi
Author
Francesco Marano, Donato Di Pasquale
Affected product
Visual Email Designer for WooCommerce < 1.7.2 (WordPress plugin)
References
Product description
Visual Email Customizer for WooCommerce is a WordPress plugin for designing custom email teamplates to replace standard WooCommerce emails.
Vulnerability details
Analyzing the code, the file "sm-ajax.php" used to register the hooks (action and filter) of the plugin immediately caught our attention. It uses a small snippet of code repeated about 50 times in order to do this, each time specifying a different hook and its corresponding function:
sm-ajax.phpadd_action('wp_ajax_NOME_DELLA_ACTION_AJAX', array( __CLASS__, 'NOME_DELLA_FUNZIONE_ASSOCIATA' ));
The reason we were particularly interested in this file is that using such a simple code snippet repeated so many times instead of using a loop and dictionary (or some equivalent data structure), reveals a programmer's predilection to copy-pasting rather than writing clean, maintainable code. In such cases, if a vulnerability is found, it is reasonable to assume that vulnerability is present elsewhere.
Let's use the social_function function as an example:
sm-ajax.phpadd_action('wp_ajax_swcm_social_function', array( __CLASS__, 'social_function' ));
The code of the function is as follows:
sm-ajax.phppublic static function social_function() { check_ajax_referer('smack-woocomm-key-email-customizer', 'securekey'); global $wpdb; $get_social_id = sanitize_text_field($_POST['swcm_social_id']); $social_id = explode('_', $get_social_id); $template_type = sanitize_text_field($_POST['template_type']); $template_id = intval($_POST['template_id']); $theme_id = intval($_POST['theme_id']); $get_template_details = $wpdb->get_var("SELECT $template_type FROM {$wpdb->prefix}test_table WHERE template_id = $template_id AND theme_id = $theme_id AND status = 'temporary'"); $unser_template_details = json_decode($get_template_details, true); $social_details['iconpos'] = $unser_template_details[$get_social_id]['position']; $social_details['bgcolor'] = $unser_template_details[$get_social_id]['bgcolor']; $social_details['facebook'] = $unser_template_details[$get_social_id]['fb']; $social_details['twitter'] = $unser_template_details[$get_social_id]['twitter']; $social_details['google'] = $unser_template_details[$get_social_id]['google']; $social_details['linkedin'] = $unser_template_details[$get_social_id]['linkedin']; $social_details['skype'] = $unser_template_details[$get_social_id]['skype']; $social_details['youtube'] = $unser_template_details[$get_social_id]['youtube']; $social_details['insta'] = $unser_template_details[$get_social_id]['instagram']; $social_details['rwidth'] = $unser_template_details[$get_social_id]['rwidth']; echo wp_json_encode($social_details); wp_die(); }
It is easy to see that this function suffers from SQL injection on the template_type POST parameter being used without sanitization to build the SQL query.
While the sanitize_text_field function is a sanitizing function from a functional point of view, it has nothing to do with security. Its job is to check for non-UTF8 characters, convert "<" to "<", remove tags, delete tabs or other spacing characters, etc.
Before we analyze the code and write the exploit, we can test the hypothesis made earlier. Looking at many other functions in the same file we notice how they all have a very similar structure and are all affected by exactly the same vulnerability, on the same parameter. Let's look at another function as an example:
sm-ajax.phppublic static function image_function() { check_ajax_referer('smack-woocomm-key-email-customizer', 'securekey'); global $wpdb; $get_image_id = sanitize_text_field($_POST['swcm_image_id']); $image_id = explode('_', $get_image_id); $template_type = sanitize_text_field($_POST['template_type']); $template_id = intval($_POST['template_id']); $theme_id = intval($_POST['theme_id']); $get_template_details = $wpdb->get_var("SELECT $template_type FROM {$wpdb->prefix}test_table WHERE template_id = $template_id AND theme_id = $theme_id AND status = 'temporary'"); $unser_template_details = json_decode($get_template_details, true); $image_details['imgpos'] = $unser_template_details[$get_image_id]['img_position']; $image_details['media'] = $unser_template_details[$get_image_id]['img_media']; $image_details['bgcolor'] = $unser_template_details[$get_image_id]['img_bgcolor']; $image_details['imgwidth'] = $unser_template_details[$get_image_id]['img_width']; $image_details['imgheight'] = $unser_template_details[$get_image_id]['img_height']; $image_details['rwidth'] = $unser_template_details[$get_image_id]['rwidth']; $image_details['url'] = $unser_template_details[$get_image_id]['url']; $image_details['rounded'] = $unser_template_details[$get_image_id]['rounded']; $image_details['border'] = $unser_template_details[$get_image_id]['border']; $image_details['bordersize'] = $unser_template_details[$get_image_id]['bsize']; $image_details['bordercolor'] = $unser_template_details[$get_image_id]['bcolor']; $image_details['imgtop'] = $unser_template_details[$get_image_id]['top']; $image_details['imgbottom'] = $unser_template_details[$get_image_id]['bottom']; echo wp_json_encode($image_details); wp_die(); }
Like these there are many other functions, all similar, all vulnerable. The full list follows:
- social_function
- swcm_video_function
- footer_function
- disclaimer_function
- image_function
- customer_function
- delete_widget_function
- hr_function
- maintext_function
- multi_image_function
- button_function
- title_function
- swcm_clone_widget
- order_function
- textarea_function
We continue the analysis of the previous function in order to write a working exploit.
sm-ajax.phppublic static function social_function() { check_ajax_referer('smack-woocomm-key-email-customizer', 'securekey'); global $wpdb; $get_social_id = sanitize_text_field($_POST['swcm_social_id']); $social_id = explode('_', $get_social_id); $template_type = sanitize_text_field($_POST['template_type']); $template_id = intval($_POST['template_id']); $theme_id = intval($_POST['theme_id']); $get_template_details = $wpdb->get_var("SELECT $template_type FROM {$wpdb->prefix}test_table WHERE template_id = $template_id AND theme_id = $theme_id AND status = 'temporary'"); $unser_template_details = json_decode($get_template_details, true); $social_details['iconpos'] = $unser_template_details[$get_social_id]['position']; $social_details['bgcolor'] = $unser_template_details[$get_social_id]['bgcolor']; $social_details['facebook'] = $unser_template_details[$get_social_id]['fb']; $social_details['twitter'] = $unser_template_details[$get_social_id]['twitter']; $social_details['google'] = $unser_template_details[$get_social_id]['google']; $social_details['linkedin'] = $unser_template_details[$get_social_id]['linkedin']; $social_details['skype'] = $unser_template_details[$get_social_id]['skype']; $social_details['youtube'] = $unser_template_details[$get_social_id]['youtube']; $social_details['insta'] = $unser_template_details[$get_social_id]['instagram']; $social_details['rwidth'] = $unser_template_details[$get_social_id]['rwidth']; echo wp_json_encode($social_details); wp_die(); }
The first obstacle is the use of the check_ajax_referer function, which checks for the existence of a securekey parameter with a randomly generated value during regular use of the plugin. We can then browse the website to the plugin-related pages, intercept the traffic using, for example, Burp or the browser console, and get the value we need to use.

Once we get the value for the securekey parameter, we can proceed. We see the other parameters are unnecessary since the injection point is right after the word SELECT, so we omit them all except for template_type which is our vulnerable parameter.
We are already able to exploit the vulnerability in a blind (time-based) way so far, but is it possible to do more than that? Unfortunately, no, because to output the result of the query we need to set the POST parameter swcm_image_id to any value, for example "sqli", and the query to return a string representing a JSON in the form:
{
"sqli": {
"img_position": "QUERY-SQL"
}
}In this way we would have in output:{
"iconpos": "RISULTATO-DELLA-QUERY-SQL",
"bgcolor": null,
"facebook": null,
"twitter": null,
"google": null,
"linkedin": null,
"skype": null,
"youtube": null,
"insta": null,
"rwidth": null
}Unfortunately, the wp_magic_quotesfunction, which is enabled by default on all WordPress installations, prevents the use of quotes by escaping them. This implies that we can exploit the vulnerability as long as we do not use quotes, but we cannot manipulate the output in the way we need to turn the SQL injection from blind to non-blind.Proof of Concept
It is possible to exploit the vulnerability using SQLMap:
$ export COOKIES='YOUR-SESSION-COOKIES-HERE'
$ export SECUREKEY='YOUR-SECUREKEY-HERE'
$ sqlmap -u 'https://www.target.com/wp-admin/admin-ajax.php?action=swcm_social_function' --data "securekey=${SECUREKEY}&template_type=test" -p template_type --dbms=mysql --technique=T -H "Cookie: ${COOKIES}" --level=3Disclosure timeline
22/09/2022
Vulnerability submitted to WPScan
04/11/2022
Assigned CVE-2022-3860
21/11/2022
Official fix released
09/12/2022
Advisory published by WPScan
23/12/2022
Proof of Concept (PoC) published
