← All posts

हमने MFA अनिवार्य किया। एक query parameter ने उसे बंद कर दिया।

Authagonal·July 14, 2026
securitymfaoidcwar-story

हमने multi-factor authentication शिप किया। Password के बाद login page second factor का challenge देता था, बिल्कुल वैसे ही जैसे आप उम्मीद करेंगे। हमने टेस्ट किया, काम कर गया, हम आगे बढ़ गए। फिर, लॉन्च से पहले अपने ही auth server की जांच के दौरान, हमें पता चला कि सिर्फ एक URL एडिट करके पूरी चीज़ को skip किया जा सकता है।

मामला कुछ यूं है। OIDC flow में आपकी application user को /connect/authorize पर भेजती है। अगर वह अभी sign in नहीं है, तो server उसे login page पर भेज देता है, और असली destination एक parameter में समेट दी जाती है: /login?returnUrl=/connect/authorize?...। आप password डालते हैं, MFA पूरा करते हैं, और login flow आपको उसी returnUrl पर लौटा देता है, जहां /connect/authorize एक authorization code issue करता है और उसके पीछे tokens आते हैं।

Bug operations के क्रम में छिपा था। Password वाला step आपको sign in कर देता था। यानी वह एक authenticated cookie सेट कर देता था। MFA उस sequence का बस अगला page था। और /connect/authorize, यानी वह endpoint जो असल में tokens बांटता है, ऐसा करने से पहले सिर्फ एक ही सवाल पूछता था: क्या यह request authenticated है? उसने कभी यह नहीं पूछा कि session ने second factor पार किया है या नहीं। "प्रमाणित" और "MFA के साथ प्रमाणित" एक ही cookie थे।

तो यह bypass कोई चालाकी नहीं है। आप अपना password submit करते हैं। आपको authenticated cookie मिल जाती है। फिर, flow के साथ MFA prompt तक जाने के बजाय, आप खुद सीधे उस returnUrl पर चले जाते हैं, /connect/authorize?..., वही पता जो server ने आपको शुरुआत में ही थमा दिया था। Authorize को एक authenticated principal दिखता है, वह अपनी इकलौती जांच चलाता है, और code issue कर देता है। Second factor कभी होता ही नहीं। MFA prompt गलियारे का एक पड़ाव भर था, और उसे घूमकर पार कर जाने से आपको कोई नहीं रोकता था।

यह टेस्टिंग से इसलिए बच निकलता है क्योंकि happy path एकदम कसा हुआ है। इंसान की तरह interface में क्लिक करते जाइए और हर बार challenge मिलेगा। Gate असली है। बस गलत जगह पर है। किसी OIDC server में security boundary login screen नहीं होती। वह /connect/authorize होती है, वह बिंदु जहां session tokens में बदलता है। जो भी requirement सिर्फ उस boundary तक ले जाने वाले pages में रहती है, वह महज़ सलाह है, क्योंकि जो कोई underlying request बना सकता है, वह उसे सीधे भी बना सकता है।

यही नज़रिया बदलना असल फिक्स है। MFA कोई step नहीं है जिसे आप करते हैं। यह एक property है जो session के पास या तो है या नहीं है। तो हमने इसे page की तरह model करना बंद किया और claim की तरह model करना शुरू किया। जब आप second factor पार करते हैं, तो login flow auth cookie में एक mfa_authenticated marker लिख देता है। /connect/authorize अब "प्रमाणित" से काम नहीं चलाता। उसे चाहिए "प्रमाणित और वह marker साथ लिए हुए"। सिर्फ password वाली cookie अब token endpoint पर बेअसर है: आप उसे किसी भी URL पर ले जाएं, वह MFA पूरा करने के लिए वापस भेज दी जाती है, क्योंकि उसमें कमी एक claim की है, किसी page visit की नहीं।

Federation इसमें से साफ-साफ निकल आता है। जब कोई user ऐसे external identity provider के ज़रिए sign in करता है जिसने अपना MFA खुद चलाया है, तो वह provider second factor की गवाही देता है, इसलिए federated sign-in वही marker सेट करता है। SSO users से उस चीज़ के लिए दोबारा challenge नहीं होता जो उनका IdP पहले ही लागू कर चुका है। Marker ही single source of truth है, और हर वह path जो MFA को जायज़ तौर पर पूरा कर सकता है, उसे लिखता है।

काम की सीख का MFA से खास लेना-देना नहीं है। सीख यह है: control उस boundary पर लागू करें जो वह चीज़ देती है जिसकी आप हिफाज़त कर रहे हैं, न कि उस interface step पर जो वहां तक ले जाने के लिए बना है। जांच हमारे पास थी। हमने MFA logic लिखा था, टेस्ट किया था, शिप किया था। वह बस system के उस हिस्से से जुड़ा था जिसे attacker को इस्तेमाल करने की ज़रूरत ही नहीं। Consent screens, terms का acceptance, sensitive scopes के लिए step-up: सबका failure mode यही है। अगर credential issue करने वाला endpoint शर्त की पुष्टि नहीं करता, तो उसे मांगने वाली screen महज़ एक सुझाव है।

यह वाला हमने किसी customer के टकराने से पहले पकड़ लिया, उसी audit में जिसने इसके कुछ और भाई-बंधु भी बाहर निकाले। यही दलील है कि अपने ही auth पर ऐसे नज़र डालनी चाहिए जैसे आप उस पर हमला कर रहे हों, और खासकर हर उस endpoint पर जो कुछ कीमती बांटता है, यह नहीं पूछना चाहिए कि "क्या user flow से होकर गुज़रा", बल्कि यह कि "यह request असल में क्या साबित करती है"। हमारी request एक password साबित करती थी। हम उसे दो factors का सबूत मान रहे थे।

MFA को उस endpoint पर लागू करना जो tokens mint करता है, न कि उस page पर जो उसे मांगता है, असली MFA और महज़ एक सुझाव के बीच का पूरा फर्क यही है। Authagonal इसे वहीं लागू करता है जहां credential issue होता है, इसलिए हाथ से एडिट किया गया return URL कहीं नहीं पहुंचता।