This blog post is about the WooCommerce Stripe Gateway plugin vulnerability. If you’re a WooCommerce Stripe Gateway user, please update the plugin to at least version 7.4.1.
Patchstack paid plan users are protected from the vulnerability. You can also sign up for the Patchstack Community plan to be notified about vulnerabilities as soon as they become disclosed.
For plugin developers, we have security audit services and Threat Intelligence Feed API for hosting companies.
About the WooCommerce Stripe Gateway WordPress plugin
The plugin WooCommerce Stripe Gateway (versions 7.4.0 and below, free version), which has over 900,000 active installations is known as the most popular WooCommerce Stripe payment plugin in WordPress. This plugin is developed by WooCommerce.
This plugin is a WordPress plugin which allows you to accept payments directly on a store for web and mobile. With the plugin, customers can stay on the store during checkout instead of being redirected to an externally hosted checkout page.
The security vulnerability in WooCommerce Stripe Gateway
This plugin suffers from an Unauthenticated Insecure Direct Object Reference (IDOR) vulnerability. This vulnerability allows any unauthenticated user to view any WooCommnerce order’s PII data including email, user’s name, and full address. The described vulnerability was fixed in version 7.4.1 with some backported fixed version and assigned CVE-2023-34000.
Find out more from the Patchstack database.
The first underlying vulnerability is located on javascript_params
function :
public function javascript_params() {
global $wp;
$order_id = absint( get_query_var( 'order-pay' ) );
$stripe_params = [
'title' => $this->title,
'key' => $this->publishable_key,
'i18n_terms' => __( 'Please accept the terms and conditions first', 'woocommerce-gateway-stripe' ),
'i18n_required_fields' => __( 'Please fill in required checkout fields first', 'woocommerce-gateway-stripe' ),
'updateFailedOrderNonce' => wp_create_nonce( 'wc_stripe_update_failed_order_nonce' ),
'updatePaymentIntentNonce' => wp_create_nonce( 'wc_stripe_update_payment_intent_nonce' ),
'orderId' => $order_id,
'checkout_url' => WC_AJAX::get_endpoint( 'checkout' ),
];
// If we're on the pay page we need to pass stripe.js the address of the order.
if ( isset( $_GET['pay_for_order'] ) && 'true' === $_GET['pay_for_order'] ) { // wpcs: csrf ok.
$order_id = wc_clean( $wp->query_vars['order-pay'] ); // wpcs: csrf ok, sanitization ok, xss ok.
$order = wc_get_order( $order_id );
if ( is_a( $order, 'WC_Order' ) ) {
$stripe_params['billing_first_name'] = $order->get_billing_first_name();
$stripe_params['billing_last_name'] = $order->get_billing_last_name();
$stripe_params['billing_address_1'] = $order->get_billing_address_1();
$stripe_params['billing_address_2'] = $order->get_billing_address_2();
$stripe_params['billing_state'] = $order->get_billing_state();
$stripe_params['billing_city'] = $order->get_billing_city();
$stripe_params['billing_postcode'] = $order->get_billing_postcode();
$stripe_params['billing_country'] = $order->get_billing_country();
}
}
-----------------------------------------------------------------------------------------
Notice that the code will fetch an order object to $order
variable using the $order_id
variable. The $order_id
variable is constructed from $wp->query_vars['order-pay']
. According to the query_vars documentation, this hook could be used to fetch parameter from the GET parameters.
The code then will construct a $stripe_params
variable with details from the $order
object such as user’s full name and full address. There is no orders ownership check on the rest of the function code and the function will return $order
as an object.
When traced, the javascript_params
variable could be called from the payment_scripts
function:
public function payment_scripts() {
-----------------------------------------------------------------------------------------
wp_localize_script(
'woocommerce_stripe',
'wc_stripe_params',
apply_filters( 'wc_stripe_params', $this->javascript_params() )
);
-----------------------------------------------------------------------------------------
The wp_localize_script function will return a JavaScript object variable to the front-end. The overall function call could be triggered from the site’s front page and the order’s PII disclosure will be reflected back into the page source.
The second vulnerable code exist in the payment_fields
function:
public function payment_fields() {
global $wp;
$user = wp_get_current_user();
$display_tokenization = $this->supports( 'tokenization' ) && is_checkout() && $this->saved_cards;
$total = WC()->cart->total;
$user_email = '';
$description = $this->get_description();
$description = ! empty( $description ) ? $description : '';
$firstname = '';
$lastname = '';
// If paying from order, we need to get total from order not cart.
if ( isset( $_GET['pay_for_order'] ) && ! empty( $_GET['key'] ) ) { // wpcs: csrf ok.
$order = wc_get_order( wc_clean( $wp->query_vars['order-pay'] ) ); // wpcs: csrf ok, sanitization ok.
$total = $order->get_total();
$user_email = $order->get_billing_email();
} else {
if ( $user->ID ) {
$user_email = get_user_meta( $user->ID, 'billing_email', true );
$user_email = $user_email ? $user_email : $user->user_email;
}
}
if ( is_add_payment_method_page() ) {
$firstname = $user->user_firstname;
$lastname = $user->user_lastname;
}
ob_start();
echo '<div
id="stripe-payment-data"
data-email="' . esc_attr( $user_email ) . '"
data-full-name="' . esc_attr( $firstname . ' ' . $lastname ) . '"
data-currency="' . esc_attr( strtolower( get_woocommerce_currency() ) ) . '"
>';
The condition is the same as the first vulnerable code, there is no orders ownership check on the code and the function will output the billing email and user’s full name to the front-end.
The patch in WooCommerce Stripe Gateway
Since the issue is mainly because of the code not validating the fetched order ownership, checking the endpoint using the is_valid_pay_for_order_endpoint
function which will check the order based on the key and ownership should fix the issue. The two patches could be seen here and here:
Conclusion
One of the critical flow for WooCommerce related plugin is handling order object. In many cases, order object is referenced from user input that is coming from WordPress query_vars. Make sure to always check access control around order object by checking the order key and ownership.
Timeline
Help us make the internet a safer place
Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.
- If you’re a plugin developer, join our mVDP program that makes it easier to report, manage and address vulnerabilities in your software.
- If you’re a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.