WooCommerce is the most popular e-commerce plugin for WordPress with over 5 million installations. A flaw in the way WooCommerce handles imports of products results in a stored cross-site scripting vulnerability (XSS) that can be exploited through cross-site request forgery (CSRF).
In WooCommerce shop managers and administrators have the ability to import (insert/update) products via a .csv
file. Every product in WooCommerce has a product description where the shop manager can insert limited HTML, i.e. very basic HTML tags and attributes, such as the <a>
tag in combination with the href
attribute. It is important to mention that the administrator is able to use unfiltered HTML in the WordPress default installation.
An attacker can use CSRF to import (insert/update) any product via a .csv
file. The attacker needs to upload a .csv
file which is possible with a user of the role author or higher. If the attacker tricks an administrator of a targeted blog into visiting a malicious website set up by the attacker he can import products with unsanitized HTML in the product description via CSRF. Finally, this leads to a stored XSS in every product of the vulnerable shop.
Technical Analysis
The importer functionality consists of 4 steps which are processed in the given order:
- Upload a CSV file (upload)
- Column mapping (mapping)
- Import (import)
- Done! (done)
The words in the parentheses are used as function name in the WooCommerce product importer.
Bypassing the Nonce
The importer of WooCommerce uses the PHP function call_user_func()
to call the different steps of the importing process. The first step of the importer (upload) is protected by a nonce (anti-CSRF token), however, the other steps are not protected.
The following code snippet shows the invokation of call_user_func()
:
/includes/admin/importers/class-wc-product-csv-importer-controller.php
The array $this->steps
is a whitelist and consists of the different importer steps described above. The attacker controlled variable is $this->step
, this means we can only call functions listed in the view
field from an WC_Product_CSV_Importer_Controller
($this
) object. However, we can skip the upload step of the importer and go directly to the import() function from the import step.
CSRF with Self-Created Nonce
The import()
function localizes and enqueues the wc-product-import
JavaScript with attacker controlled inputs and a valid nonce which leads to CSRF.
/includes/admin/importers/class-wc-product-csv-importer-controller.php
The function wp_localize_script()
localizes a registered script with data for a JavaScript variable. In simple terms, all the data in the wc_product_import_params
variable are controlled by an attacker. Furthermore, a valid import_nonce
is created with the wp_create_nonce()
function in line 407 for the wc-product-import
action. Finally, the JavaScript is enqueued in line 417 and sends an AJAX request to the WordPress backend with the attacker controlled $_POST
variable and the valid nonce.
/includes/admin/class-wc-admin-importers.php
The invoked AJAX request calls the do_ajax_product_import()
function. In line 202 the nonce check of the check_ajax_referer()
function is bypassed via the self-created nonce described above. In line 204 the code checks if the current user has the privileges to import products. This is the case because the AJAX request is invoked by the victim’s browser (administrator). All used parameters like $_POST['file']
are provided by the wp_localize_script()
described above. Finally, the products from the malicious .csv
file are imported with the XSS payload in the product description.
Timeline
Date | What |
2019/05/29 | First contact with vendor |
2019/05/29 | Response of vendor |
2019/06/27 | Insufficient patch proposed |
2019/06/29 | Bypass #1 reported and acknowledged |
2019/07/01 | Vendor proposed a valid fix |
2019/07/02 | Fix with version 3.6.5 released |
Summary
The introduced vulnerabilities can lead to stored XSS in every product of the shop. This allows an attacker to execute arbitrary JavaScript code in the browser of the administrator who triggered the CSRF vulnerability on the target website or any visitor of the shop, and as a result to send HTTP requests using the session of the victim. All of the JavaScript execution happens in the background without the victims noticing. The mistake was to only protect the first step of the import functionality via a nonce, but not the others. At a first glance, it does not seem tragic but a sophisticated attacker could abuse this small mistake to compromise blogs. It should be noted that WordPress allows administrators of a blog to directly edit the .php
files of themes and plugins from within the admin dashboard. By abusing the XSS vulnerability, the attacker can gain arbitrary PHP code execution on the remote server.