XML External Entity (XXE) इंजेक्शन (CWE-611): यह कैसे काम करता है और इससे कैसे बचें
जो XML parser किसी document के DTD को मानता है, वह file:// या http:// URLs से SYSTEM entities लाकर नतीजे को parse किए डेटा में चिपका देता है, इसलिए अपलोड किया गया document सर्वर की फ़ाइलें पढ़ सकता है या सर्वर से आंतरिक पतों को कॉल करवा सकता है। external entities और entity expansion कैसे काम करते हैं, parser की कौन-सी settings इनकी वजह हैं, और forbid_dtd=True के साथ defusedxml इन्हें कैसे बंद करता है, Flask के उदाहरण के साथ।
आसान भाषा में (ELI5)
एक छापाख़ाना फ़ॉर्म-पत्र भरता है। एक ग्राहक के पत्र में लिखा है: 'प्रिय [यहाँ अपनी फ़ाइल-अलमारी की दराज़ 4 की सामग्री डालें]'। सावधान मुद्रक केवल ग्राहक के दिए शब्द डालता है। लापरवाह मुद्रक दराज़ 4 तक जाता है, जिसमें दूसरे ग्राहकों के अनुबंध रखे हैं, और उन्हें पत्र में छापकर डाक से वापस भेज देता है। XXE वही लापरवाह मुद्रक है: XML document parser से कहता है कि कोई फ़ाइल या वेब पता लाकर डाल दो, और parser ऐसा कर देता है। उपाय यह है कि 'कहीं और से डालो' जैसे निर्देश वाले हर पत्र को ठुकरा दिया जाए।
इस पेज के मुख्य शब्द
- DTD और entities
<!DOCTYPE ...>में लिखी Document Type Definition entities घोषित कर सकती है, यानी नाम वाले टेक्स्ट टुकड़े जिन्हें parser हर उस जगह रख देता है जहाँ&नाम;आता है। internal entities में सीधा टेक्स्ट होता है; external entities कहीं और इशारा करती हैं।- External entity
<!ENTITY x SYSTEM "file:///etc/hostname">parser से कहता है कि वह URL पढ़े और उसकी सामग्री वहाँ रखे जहाँ&x;इस्तेमाल हुआ है।http://URLs के साथ रिक्वेस्ट सर्वर भेजता है, जिससे XXE सर्वर-साइड रिक्वेस्ट फ़ॉर्जरी बन जाता है।- Entity expansion (Billion Laughs)
- ऐसी entities जो दूसरी entities को दस-दस बार बुलाती हैं, एक किलोबाइट के document को memory में गीगाबाइट्स टेक्स्ट में फैला देती हैं (CWE-776)। इसके लिए बाहरी पहुँच ज़रूरी नहीं, इसलिए केवल DTDs ठुकराना या expansion सीमित करना ही इसे रोकता है।
- Parser की डिफ़ॉल्ट settings
- व्यवहार library पर निर्भर है: lxml 5 डिफ़ॉल्ट रूप से केवल internal entities resolve करता है, पर
resolve_entities=Trueऔरload_dtd=Trueछेद फिर खोल देते हैं; Java काDocumentBuilderFactoryexternal entities resolve करता है जब तकdisallow-doctype-declसेट न हो; और .NET कोDtdProcessing.Prohibitचाहिए। - defusedxml
- एक Python package जो मानक parsers को लपेटता है और ख़तरनाक constructs को process करने के बजाय
EntitiesForbidden,ExternalReferenceForbiddenयाDTDForbiddenउठाता है।forbid_dtd=TrueDOCTYPE वाले हर document को ठुकरा देता है।
हमले का चरण-दर-चरण प्रवाह
हमलावर एक XML document अपलोड करता है
वह एक external entity घोषित करता है और उसे ऐसे field में इस्तेमाल करता है जिसे सर्वर वापस लौटाता है। file:///etc/hostname की ओर इशारा करने वाली entity फ़ाइल पढ़ने का मामला दिखाती है।
parser DTD को process करता है
चूँकि ऐप्लिकेशन ने DTD loading और entity resolution चालू कर रखे हैं, parser बताई गई फ़ाइल या URL पढ़ लेता है।
फ़ाइल की सामग्री डेटा बन जाती है
टेक्स्ट customer element में पहुँचता है, और endpoint उसे अपने JSON response में लौटा देता है।
हमलावर फ़ाइलें पढ़ता है या आंतरिक hosts तक पहुँचता है
configuration फ़ाइलें, keys और क्लाउड metadata इसी तरह पढ़े जा सकते हैं। 2014 में Reginaldo Silva को Facebook से 33,500 डॉलर का इनाम मिला, एक ऐसे XXE के लिए जो OpenID handling में था और सर्वरों की फ़ाइलें पढ़ सकता था।
सोर्स कोड: कमज़ोर बनाम सुरक्षित कार्यान्वयन
# invoices.py: अपलोड किया DTD जो माँगे, parser वही करता है
from flask import Flask, request
from lxml import etree
app = Flask(__name__)
# entities resolve, DTDs लोड और network access की अनुमति
@app.post("/invoices/import")
def import_invoice():
parser = etree.XMLParser(resolve_entities=True, load_dtd=True, no_network=False)
root = etree.fromstring(request.get_data(), parser)
return {"customer": root.findtext("customer"), "total": root.findtext("total")}
# invoices.py: DTD वाले documents parse से पहले ठुकरा दिए जाते हैं
from defusedxml import DefusedXmlException
from defusedxml.ElementTree import ParseError, fromstring
from flask import Flask, abort, request
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 1_000_000
# न DOCTYPE, न entities, न external references; body 1 MB तक सीमित
@app.post("/invoices/import")
def import_invoice():
try:
root = fromstring(request.get_data(), forbid_dtd=True)
except (DefusedXmlException, ParseError):
abort(400)
return {"customer": root.findtext("customer"), "total": root.findtext("total")}
इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट
- Python में अविश्वसनीय XML को
defusedxmlसे parse करें, बाक़ी जगह DTDs बंद करके: Java मेंdisallow-doctype-declको true, .NET मेंDtdProcessing.Prohibit, और libxml2 बिनाXML_PARSE_NOENTवXML_PARSE_DTDLOADके। - जिस इनपुट पर आपका नियंत्रण नहीं उसके लिए lxml को कभी
resolve_entities=True,load_dtd=Trueयाno_network=Falseन दें, और CI में Bandit चलाएँ (B320lxml.etreeको चिह्नित करता है)। - रिक्वेस्ट का आकार सीमित करें (
MAX_CONTENT_LENGTH) और DOCTYPE वाला हर document ठुकराएँ। - अपलोड की गई SVG, SAML, DOCX और XLSX फ़ाइलों को XML मानें और उन्हें उन्हीं मज़बूत settings से parse करें।
- CI में ऐसा टेस्ट जोड़ें जो
file:///etc/hostnameकी ओर इशारा करने वाली external entity वाला document भेजे और 400 की अपेक्षा करे।