Post

Customer Account Takeover in Shopify Stores

Summary

During the recent Ambassador World Cup held by HackerOne, we identified an account takeover vulnerability in Shopify affecting a subset of Shopify’s Shop users. A successful exploit would have allowed attackers to takeover accounts of Shop’s users in public Shopify stores allowing access to order history and shipping addresses.

Description

Shopify recently introduced Shop Pay within the Shop application. Shop Pay allows users to easily purchase items in most Shopify stores by storing their payment information in their Shop account. However, Shop accounts, by default, do not have Shop Pay enabled. Users must manually enable this feature in their Shop settings or when purchasing an item from a store that supports Shop Pay.

Shop Pay versus Shop Account

When testing the difference between Shop Pay and Shop accounts, we noticed that it was possible to sign up as a Shop Pay user with an email address of a Shop user. During this process, Shop Pay would ensure the email address has an associated Shop account. The user can proceed with account creation if the email exists and does not use Shop Pay. In order to create a Shop Pay account, the user will have to provide an email address of a valid Shop account and a phone number to use during login.

When the user creates a Shop Pay account for an existing Shop user, an email verification URL is sent to the email address and the account will also display a banner asking to verify the email:

Shop Pay Example

While the API response marked the email as unverified, it does not limit the user from logging into the “Shop Pay” account. Due to this, we explored cases where we could use the unverified account to log in. Ideally, this is what would result in an ATO. Upon reviewing the Shop Pay feature, we noticed that enabling Shop Pay as a payment provider for a store also enabled Shop Pay’s OAuth workflow.

Shop Pay OAuth Workflow

The OAuth workflow, if used, allows customer accounts (accounts of user in the specific store) to be created directly via the Shop Pay Account. Even though a store may not have the “Sign in with Shop” fully enabled and displayed in their store, it is possible to access the Shop Pay OAuth directly through: https://STORE.myshopify.com/services/login_with_shop/authorize?target_origin=https%3A%2F%2Fophion-security.myshopify.com&api_key=123&locale=en&analytics_trace_id=d59928be-d6fb-45f2-aba6-1e9cf96f677d&analytics_context=loginWithShopClassicCustomerAccounts&flow=default&email_verification_required=true&sign_up_enabled=false

Visiting the URL redirected the user to pay.shopify.com OAuth via the Shop Pay SDK:

Shop Pay Redirect

After a successful login to the Shop Pay account, a request is sent to services/login_with_shop/finalize endpoint to retrieve the Shop Pay session cookie (login_with_shop_finalize and shopify_pay). These were then exchanged to get the _secure_session_id to login as the customer in the store through the following GET request:

1
GET /services/login_with_shop/finalize HTTP/2 Host: STORE.myshopify.com Cookie: login_with_shop_finalize=LOGIN_WITH_FINALIZE_COOKIE_HERE; shopify_pay=SHOP_PAY_COOKIE_HERE;  Sec-Ch-Ua: "Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Sec-Ch-Ua-Platform: "macOS" Accept: */* Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9

Proof of Concept

To confirm our vulnerability, we tested this against a store that had Shop Pay enabled. We went to the store’s account registration page and created a brand-new customer account. We then created a fictitious shipping address for the new customer account:

Test PoC Account

This account was associated with a new email address: [email protected] (changed for privacy reasons). This specific email address had a regular Shop account but not a Shop Pay account.

From our “attacker” point of view, we went to https://shop.app/pay/authentication/login and created an account with the email [email protected] with a phone number we controlled.

Attacker Account

We then proceeded to log in with the Shop Pay account against the store via https://pay.shopify.com/pay/sdk-authorize?target_origin=https%3A%2F%2FSTORE.myshopify.com&locale=en&analytics_trace_id=ANALYTICS_IDd&analytics_context=loginWithShopClassicCustomerAccounts&flow=default&email_verification_required=true&sign_up_enabled=false&response_mode=form_post&scope=openid+email+phone+pay%3Asession_token&response_type=id_token&client_id=CLIENT_ID&redirect_uri=https%3A%2F%2FSTORE.myshopify.com%2Fservices%2Flogin_with_shop%2Fcallback&state=STATE&version=1.

Since the store did not use Shop Pay OAuth by default for customer accounts, we had to retrieve the login_with_shop_finalize and shopify_pay session values. These session values were then exchanged to retrieve the _secure_session_id. Once we had the _secure_session_id cookie, we could access the Shopify store website as that user and get access to past orders and shipping address of the users.

Takeover PoC Final

Conclusion

This vulnerability was responsibly disclosed to Shopify’s team and has been patched. A successful exploitation required that a vulnerable user have a Shop App account to track orders. A successful exploitation of this vulnerability would allow malicious actors access to users’ PII.

This post is licensed under CC BY 4.0 by the author.