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.

READ
AMD Investigates Alleged Data Breach, Stolen Company Data Claims Emerge

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:

Buy Me A Coffee
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.

READ
CERT-In Finds Multiple Bugs in Google Chrome, SAP Products

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.