FlawOpen

flawopen.com/Incidents/capital-one-ssrf-imds-july-2019

● Capital One · CWE-918 · July 2019 · अति गंभीर
सुरक्षा अनुसंधान · FlawOpen

Capital One SSRF: AWS IMDSv1 क्रेडेंशियल चोरी

संघीय आपराधिक शिकायत में आरोप है कि गलत कॉन्फ़िगर WAF ने SSRF को AWS IMDSv1 तक पहुँचाया और अस्थायी भूमिका-क्रेडेंशियल तथा लगभग 10.6 करोड़ लोगों का S3 डेटा उजागर किया। टोकन अनिवार्य करें और भूमिकाएँ सीमित रखें।[10], [2]

💡 आसान भाषा में (ELI5)

DOJ की आपराधिक शिकायत में इस तरह की श्रृंखला का आरोप है: एक ऐसे रिसेप्शनिस्ट की कल्पना करें जो आपके बताए पते से दस्तावेज़ ले आता है। अनुरोध को आगे भेजने की सुविधा किसी को उसे इमारत के अंदरूनी कमरे तक निर्देशित करने दे सकती है। वहाँ रिसेप्शनिस्ट को वर्कलोड के लिए अस्थायी प्रवेश-बैज मिल सकता है; वह बैज केवल वही कमरे खोलता है जिनकी अनुमति उस वर्कलोड की भूमिका देती है। गलती यह मानना थी कि कमरे तक केवल कर्मचारी पहुँच सकते हैं, जबकि रिसेप्शनिस्ट को वहाँ भेजा जा सकता था।[10], [8]

इस पेज के मुख्य शब्द

SSRF
सर्वर-साइड रिक्वेस्ट फ़ॉर्जरी (CWE-918): सर्वर से हमलावर का चुना हुआ अनुरोध भिजवाना, ताकि उस अनुरोध को सर्वर की नेटवर्क स्थिति और भरोसा मिल जाए।[9]
वेब एप्लिकेशन फ़ायरवॉल
वेब एप्लिकेशन के आगे लगा फ़िल्टर। आपराधिक शिकायत में आरोप है कि Capital One का गलत कॉन्फ़िगर WAF आंतरिक पतों पर अनुरोध आगे भेज सकता था।[10]
IMDSv1
IMDSv1 चालू होने पर मेटाडेटा अनुरोधों के लिए टोकन आवश्यक नहीं होता। IAM भूमिका का सुरक्षा-क्रेडेंशियल पथ उस भूमिका से जुड़े अस्थायी क्रेडेंशियल लौटाता है।[7], [8]
IMDSv2
IMDSv2 में PUT अनुरोध से प्राप्त सत्र टोकन का उपयोग होता है। बिना टोकन वाले IMDSv1 अनुरोधों को अस्वीकार करने के लिए HttpTokens=required सेट करें; इससे केवल साधारण GET भेज सकने वाले कई SSRF रिले रुकते हैं, पर SSRF का पूरा जोखिम समाप्त नहीं होता।[7], [6]
IAM रोल क्रेडेंशियल
किसी इंस्टेंस को जारी की गई अस्थायी AWS चाबियाँ। रोल जो कुछ कर सकता है, चाबियाँ रखने वाला भी उनकी समय-सीमा ख़त्म होने तक वही कर सकता है।[8]

फ्रेमवर्क मैपिंग

  • इस गाइड में बताई गई कमजोरी के लिए CWE संदर्भ: CWE-918
  • D3-OTF आउटबाउंड ट्रैफ़िक फ़िल्टरिंग — इस गाइड में दिया गया आउटबाउंड नेटवर्क प्रतिबंध सेवा या होस्ट के पहुँच योग्य गंतव्यों को सीमित करता है।

घटना का समयक्रम (Timeline)

2019-03-22

आपराधिक शिकायत के अनुसार, 22–23 मार्च को आरोपी ने SSRF से फ़ायरवॉल भूमिका-क्रेडेंशियल प्राप्त किए और Capital One का S3 डेटा कॉपी किया।[10], [2]

2019-07-17

एक बाहरी शोधकर्ता Capital One के ज़िम्मेदार खुलासा कार्यक्रम के ज़रिए उजागर डेटा की सूचना देता है।[2]

2019-07-29

Capital One घटना की घोषणा करता है और FBI सिएटल में एक संदिग्ध को गिरफ़्तार करता है।[1]

हमले का चरण-दर-चरण प्रवाह

चरण 1

फ़ायरवॉल एक गढ़ा हुआ अनुरोध आगे भेजता है

आपराधिक शिकायत में आरोप है कि गलत कॉन्फ़िगर WAF से हमलावर द्वारा चुने गए पतों, जिनमें आंतरिक पते भी शामिल थे, पर अनुरोध भेजे जा सकते थे।[10]

चरण 2

मेटाडेटा सेवा क्रेडेंशियल सौंप देती है

शिकायत के अनुसार, अनुरोध इंस्टेंस मेटाडेटा सेवा के IAM भूमिका-क्रेडेंशियल पथ तक पहुँचा। IMDSv1 में सत्र टोकन आवश्यक नहीं था, इसलिए अनुरोध से जुड़ी भूमिका के अस्थायी क्रेडेंशियल लौट सकते थे।[10], [8]

चरण 3

रोल बहुत ज़्यादा पढ़ सकता है

शिकायत में आरोप है कि क्रेडेंशियल में भूमिका की अनुमतियाँ थीं, जिनमें ग्राहक डेटा वाले S3 बकेट सूचीबद्ध करना और पढ़ना शामिल था।[10], [8]

चरण 4

डेटा बड़े पैमाने पर बाहर जाता है

2005 से 2019 की शुरुआत तक के क्रेडिट कार्ड आवेदनों का डेटा कॉपी हुआ: अमेरिका में लगभग 10 करोड़ और कनाडा में 60 लाख लोग, जिनमें क़रीब 1,40,000 अमेरिकी सोशल सिक्योरिटी नंबर और 80,000 जुड़े बैंक खाता नंबर शामिल थे।[2]

सोर्स कोड: कमज़ोर बनाम सुरक्षित कार्यान्वयन

यह उदाहरण Flask कोड है, Capital One का कॉन्फ़िगरेशन नहीं। होस्ट allowlist अनुप्रयोग का मुख्य नियंत्रण है। आवश्यक egress proxy को कनेक्शन के समय DNS से मिले पते पर नीति लागू करनी चाहिए, instance metadata सहित private और link-local रेंज रोकनी चाहिए, और हर अनुरोध और retry पर यही जाँच दोहरानी चाहिए; proxy configuration से इस व्यवहार की पुष्टि करें। Redirect का अनुसरण नहीं किया जाता। ऐप में अलग से DNS जाँच करके बाद में कनेक्ट करने पर DNS rebinding की race बनी रहती है।[9], [5], [6]

कमज़ोर कार्यान्वयन
import requests
from flask import Flask, request

app = Flask(__name__)


@app.get("/fetch")
def fetch():
    # सर्वर कॉल करने वाले द्वारा दिया गया कोई भी URL प्राप्त करता है।
    url = request.args["target"]
    resp = requests.get(url, timeout=5)
    return resp.text
सुरक्षित और सुदृढ़ फ़िक्स
from urllib.parse import urlsplit

import requests
from flask import Flask, abort, request

app = Flask(__name__)
ALLOWED_HOSTS = {"images.example-cdn.com", "api.partner.example"}



EGRESS_PROXY = "http://egress-proxy.internal:3128"
# इस प्रॉक्सी को DNS समाधान के बाद गंतव्य नीति लागू करने के लिए कॉन्फ़िगर करें।

@app.get("/fetch")
def fetch():
    parts = urlsplit(request.args.get("target", ""))
    if parts.scheme != "https" or parts.hostname not in ALLOWED_HOSTS:
        abort(400, "destination not allowed")
    if parts.port not in (None, 443) or parts.username or parts.password:
        abort(400, "destination not allowed")
    resp = requests.get(parts.geturl(), timeout=(2, 5), allow_redirects=False, proxies={"https": EGRESS_PROXY}, trust_env=False)
    if resp.is_redirect:
        abort(502, "upstream redirect refused")
    return resp.text

इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट

स्रोत