Security analysts at Patchstack have discovered a bug in the WooCommerce Stripe Gateway plugin.
The plugin has over 900,000 active installations and is known as the most popular WooCommerce Stripe payment plugin in WordPress.
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 first underlying vulnerability is located in javascript_params
function :
includes/abstracts/abstract-wc-stripe-payment-gateway.php
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
the variable could be called from the payment_scripts
function:
includes/abstracts/abstract-wc-stripe-payment-gateway.php
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:
includes/class-wc-gateway-stripe.php
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.
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 :
One of the critical flows for WooCommerce-related plugins is handling order objects. In many cases, the order object is referenced from user input that is coming from WordPress query_vars. Make sure to always check access control around the order object by checking the order key and ownership.
Bijay Pokharel
Related posts
Recent Posts
Subscribe
Cybersecurity Newsletter
You have Successfully Subscribed!
Sign up for cybersecurity newsletter and get latest news updates delivered straight to your inbox. You are also consenting to our Privacy Policy and Terms of Use.