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:
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:
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:
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.
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.
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.