
PoC — क्रॉस-ओरिजिन अनुरोध inference-gateway में कॉन्फ़िगर किए गए प्रोवाइडर API key का पुनः उपयोग करते हैं (GHSA-5293-fcm6-fh8v, CVE-2026-87009, CVSS 5.4)।
| शोधकर्ता | Dostxodjayev Abdullox (@squeeze440) |
| सलाह | GHSA-5293-fcm6-fh8v |
| CVE | CVE-2026-87009 |
| CVSS 3.1 | 5.4 (मध्यम) — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L |
| कमज़ोरी | CWE-352, CWE-306, CWE-346 |
| स्थिति | v0.46.0 में ठीक किया गया |
सारांश: inference-gateway 0.0.0.0 पर बाइंड होता है और डिफ़ॉल्ट रूप से प्रमाणीकरण अक्षम (AUTH_ENABLED=false) के साथ आता है, और इसका ANY /proxy/:provider/*path पासथ्रू रूट बिना शर्त किसी भी कॉलर-प्रदत्त Authorization हेडर को हटा देता है और अपस्ट्रीम पर फ़ॉरवर्ड करने से पहले उसे गेटवे ऑपरेटर की स्वयं की सर्वर-कॉन्फ़िगर की गई प्रोवाइडर API कुंजी से बदल देता है, बिना किसी CORS नीति और किसी भी प्रकार की CSRF सुरक्षा के, जिससे कोई भी वेब पेज जिसे शिकार का ब्राउज़र देखता है, चुपचाप शिकार के स्वयं के OpenAI/Anthropic/आदि खाते के माध्यम से बिल किए जाने वाले LLM अनुरोध चला सकता है।
उत्पाद: inference-gateway/inference-gateway — स्व-होस्टेड, क्लाउड-नेटिव LLM गेटवे (Go, Gin)।
परीक्षण किया गया संस्करण: कमिट 6677da6afd0a606899f833c2351635edeef386f5 (main, 2026-08-04)। प्रभावित: <= 0.45.0।
तीन स्वतंत्र तथ्य मिलकर इस बग का निर्माण करते हैं।
1. प्रमाणीकरण बंद है और बाइंड डिफ़ॉल्ट रूप से सार्वजनिक है।
config/config.go:77 — AuthConfig.Enabled का डिफ़ॉल्ट false है। config/config.go:94 — ServerConfig.Host का डिफ़ॉल्ट 0.0.0.0 है। प्रमाणीकरण अक्षम होने पर, NewOIDCAuthenticatorMiddleware (api/middlewares/auth.go:27-30) OIDCAuthenticatorNoop लौटाता है, जिसका Middleware() (api/middlewares/auth.go:48-52) एक शुद्ध पासथ्रू है। इस मोड में किसी भी रूट पर कोई प्रति-अनुरोध पहचान जाँच नहीं है। क्विकस्टार्ट examples/docker-compose/basic/docker-compose.yml बिना AUTH_ENABLED सेट किए 8080:8080 प्रकाशित करता है, इसलिए प्रलेखित गेटिंग-स्टार्टेड पथ ठीक यही कॉन्फ़िगरेशन उत्पन्न करता है।
2. /proxy/:provider/*path हमेशा ऑपरेटर की स्वयं की प्रोवाइडर कुंजी इंजेक्ट करता है।
api/routes.go:102-131 (ProxyHandler) applyProviderAuth को कॉल करता है, api/routes.go:287-312:
func applyProviderAuth(req *http.Request, provider core.IProvider) error {
req.Header.Del("Authorization") // caller's own Authorization header is discarded
token := provider.GetToken() // the operator's configured key (env var, e.g. OPENAI_API_KEY)
switch provider.GetAuthType() {
case constants.AuthTypeBearer:
req.Header.Set("Authorization", "Bearer "+token)
...
ऐसा कोई कोड पथ नहीं है जहाँ कॉलर की स्वयं की क्रेडेंशियल का उपयोग किया जाता हो; डिज़ाइन हमेशा गेटवे की कॉन्फ़िगर की गई कुंजी को प्रतिस्थापित करता है। तथ्य 1 के साथ मिलकर, एक अप्रमाणित कॉलर को मुफ्त में ऑपरेटर की वास्तविक कुंजी संलग्न मिल जाती है।
3. मिडलवेयर श्रृंखला में कहीं भी कोई CORS नीति और कोई CSRF सुरक्षा मौजूद नहीं है।
cmd/gateway/main.go:271 राउटर को gin.New() के साथ बनाता है (कोई डिफ़ॉल्ट मिडलवेयर नहीं); श्रृंखला (:273-290) है otel → logger → telemetry → OIDC auth → guardrails → MCP। go.mod/go.sum में कोई CORS पैकेज नहीं है। कोई Access-Control-Allow-Origin हेडर कभी नहीं भेजा जाता। प्रॉक्सी हैंडलर को काम करने के लिए किसी कस्टम हेडर या CORS-असुरक्षित Content-Type की आवश्यकता नहीं है (यह कच्चे बॉडी को फ़ॉरवर्ड करता है, फिर api/routes.go:254 पर आउटबाउंड Content-Type को application/json में अधिलेखित करता है), इसलिए एक "सरल" क्रॉस-ओरिजिन fetch() (Content-Type: text/plain, कोई कस्टम हेडर नहीं) ब्राउज़र द्वारा बिना प्रीफ़्लाइट के भेजा जाता है। सर्वर-साइड बिल किया गया अनुरोध ब्राउज़र के रीड-साइड CORS प्रवर्तन से स्वतंत्र रूप से पूरा होता है।
शुद्ध प्रभाव: कोई भी ओरिजिन जिसे शिकार का ब्राउज़र देखता है, जबकि गेटवे उस ब्राउज़र से पहुँच योग्य है (लूपबैक, LAN, या सार्वजनिक यदि ऑपरेटर ने प्रलेखित 8080:8080 प्रकाशन पैटर्न का पालन किया है), ऑपरेटर के वास्तविक प्रोवाइडर खाते के माध्यम से मनमाने हमलावर-चयनित चैट पूर्णताएँ चला सकता है, शून्य प्रमाणीकरण के साथ और बिना किसी उपयोगकर्ता-दृश्य संकेत के।
दो भिन्न लूपबैक ओरिजिन के बीच वास्तविक क्रॉस-ओरिजिन अनुरोध करने वाले वास्तविक Chrome ब्राउज़र के साथ एंड-टू-एंड गतिशील रूप से पुष्टि की गई (गेटवे 127.0.0.1 पर, हमलावर पेज 127.0.0.2 पर)। देखें poc/:
poc/attacker_site/attack.html — हमलावर ओरिजिन से परोसा गया सटीक पेज; लोड पर इसकी एकमात्र क्रिया /proxy/openai/chat/completions पर एक fetch() है।poc/mock_upstream.py — api.openai.com का स्थान लेता है, प्राप्त Authorization हेडर, Origin, और बॉडी को लॉग करता है।poc/README.md — पूर्ण रन चरण।देखा गया: क्रॉस-ओरिजिन ब्राउज़र अनुरोध (Origin: http://127.0.0.2:8000) मॉक अपस्ट्रीम तक Authorization: Bearer sk-proj-VICTIM-REAL-BILLED-KEY-... और हमलावर-चयनित बॉडी {"messages":[{"role":"user","content":"CSRF-DRIVEBY-MARKER-8271"}]} लेकर पहुँचा — हमलावर पेज के पास कभी कोई क्रेडेंशियल नहीं था, न ही उसने देखा, न ही उससे माँगा गया। ब्राउज़र नेटवर्क निरीक्षण ने पुष्टि की कि POST http://127.0.0.1:8081/proxy/openai/chat/completions [200] 127.0.0.2:8000 पेज से चला। पूर्ण ब्राउज़र-चालित साक्ष्य (नेटवर्क लॉग, स्क्रीनशॉट) GHSA-5293-fcm6-fh8v से संलग्न है।
अपने प्रलेखित डिफ़ॉल्ट कॉन्फ़िगरेशन के साथ inference-gateway चलाने वाले किसी भी ऑपरेटर की कॉन्फ़िगर की गई प्रोवाइडर API कुंजी(कुंजियाँ) किसी भी वेब पेज द्वारा उपयोग योग्य होती हैं जो उस ब्राउज़र तक पहुँच योग्य है जो गेटवे के पोर्ट तक पहुँच सकता है, बिना किसी क्रेडेंशियल, कुकी, या विशेष नेटवर्क स्थिति के, "गेटवे के पते पर HTTP अनुरोध भेज सकता है" से परे। मूर्त रूप से: ऑपरेटर के स्वयं के प्रोवाइडर खाते पर अनधिकृत बिलिंग/कोटा खपत, किसी भी तृतीय-पक्ष वेबसाइट, विज्ञापन, या समझौता किए गए पेज द्वारा अंधाधुंध चालित, जो ऑपरेटर (या उसी LAN पर कोई भी) ने गेटवे चलने के दौरान खोला हो। ब्राउज़र हमलावर को मॉडल आउटपुट पढ़ने से रोकता है (कोई CORS हेडर नहीं), इसलिए यह एक अंधा बलपूर्वक लेनदेन है, न कि रीड प्रिमिटिव।
Origin/Sec-Fetch-Site जाँच, और बिना CORS प्रतिबंध के।AUTH_ENABLED=false हो, जो प्रलेखित डिफ़ॉल्ट है, तो /health को छोड़कर हर रूट पर शून्य प्रति-अनुरोध पहचान जाँच होती है।v0.46.0 में ठीक किया गया (मेंटेनर ने डिफ़ॉल्ट को कठोर बनाया)। अनुशंसित उपाय:
/proxy/:provider/*path (और अन्य स्टेट-चेंजिंग रूट) पर एक कस्टम, गैर-सुरक्षित-सूचीबद्ध हेडर की आवश्यकता रखें, जो क्रॉस-ओरिजिन कॉलर के लिए CORS प्रीफ़्लाइट को बाध्य करता है और गेटवे को ओरिजिन अनुमति-सूची लागू करने का स्थान देता है। यह प्रमाणीकरण सक्षम किए बिना "सरल अनुरोध" बायपास को बंद कर देता है।SERVER_HOST के डिफ़ॉल्ट को 0.0.0.0 से 127.0.0.1 में बदलें, व्यापक इंटरफ़ेस के लिए स्पष्ट ऑप्ट-इन की आवश्यकता रखें (जैसा Ollama ने इसी श्रेणी के बग के लिए किया)।AUTH_ENABLED=false हो और SERVER_HOST लूपबैक न हो, तो स्टार्टअप चेतावनी जारी करें, या शुरू करने से इनकार करें।README.md / Configurations.md में जोखिम का दस्तावेज़ीकरण करें।Dostxodjayev Abdullox (@squeeze440)