flawopen.com/xss/Python
Python में क्रॉस-साइट स्क्रिप्टिंग (XSS) (Flask, Jinja2, Django)
यूज़र इनपुट को Markup, mark_safe या render_template_string को दी गई f-स्ट्रिंग में रखने से किसी विज़िटर का टेक्स्ट दूसरे यूज़र्स के ब्राउज़र में उनके सेशन के साथ JavaScript बनकर चलता है (CWE-79)। Flask, Jinja2 और Django कहाँ अपने-आप एस्केप करते हैं, किन चार जगहों पर नहीं करते, और रिच टेक्स्ट को सुरक्षित रूप से कैसे अनुमति दें।
एक सुरक्षित फ़ॉर्म वाली छपाई की दुकान सोचिए: खाली जगह में लिखा पाठ सामान्य पाठ की तरह छपता है। लेकिन अगर कर्मचारी छपाई शुरू होने से पहले ग्राहक की पर्ची को प्रिंटर के निर्देश-पन्ने में चिपका दे, तो वह पर्ची छपाई का आदेश बन सकती है। render_template_string को दी गई Python f-string यही करती है: वह इनपुट को Jinja2 के स्रोत-पाठ में डाल देती है, इससे पहले कि autoescape उसे variable की तरह संभाले। Markup और mark_safe “HTML जाँचा जा चुका है” वाली मुहर जैसे हैं; वे जाँच करते ही नहीं। इनपुट को template variable रहने दें और autoescape को काम करने दें, या Django के छोटे HTML अंश के लिए format_html उपयोग करें।
इस पेज के मुख्य शब्द
Jinja2 autoescape- Flask
.html,.htm,.xmlऔर.xhtmlटेम्पलेट तथाrender_template_stringके लिए Jinja2 autoescaping चालू करता है, इसलिए{{ q }}<img ...>बन जाता है। अलग से बनाjinja2.Environment()तब तक autoescape बंद रखता है जब तक आपautoescape=select_autoescape()न दें। Markup / |safe / mark_safe- तीनों किसी स्ट्रिंग को पहले से सुरक्षित HTML के रूप में चिह्नित करते हैं, और टेम्पलेट इंजन उसे एस्केप करना बंद कर देता है। ये डेवलपर का वादा हैं, सैनिटाइज़र नहीं:
Markup(user_input)याmark_safe(user_input)यूज़र ने जो भी लिखा उसे छाप देता है। render_template_string(f"...")- f-स्ट्रिंग Jinja2 के पार्स करने से पहले ही मान को टेम्पलेट सोर्स में डाल देती है, इसलिए autoescape उसे कभी वेरिएबल नहीं मानता। यही लाइन सर्वर-साइड टेम्पलेट इंजेक्शन भी है:
{{ 7*7 }}जैसा मान Jinja2 हल कर देता है। django.utils.html.format_htmlstr.formatकी तरह HTML बनाता है, लेकिन पहले हर आर्ग्युमेंट कोconditional_escapeसे एस्केप करता है और फिर नतीजे को सुरक्षित चिह्नित करता है। Python कोड में छोटा HTML टुकड़ा बनाने का Django का मान्य तरीका यही है,mark_safe(f"...")के बजाय।nh3.clean()- रिच टेक्स्ट को एस्केपिंग नहीं, सैनिटाइज़र चाहिए।
nh3.clean()(Rust का ammonia) टैग और एट्रिब्यूट की अनुमति-सूची रखता है और<script>, इवेंट हैंडलर तथाjavascript:URL हटा देता है। Bleach अब deprecated है; nh3 उसका सक्रिय उत्तराधिकारी है।
फ्रेमवर्क मैपिंग
- इस गाइड में बताई गई कमजोरी के लिए CWE संदर्भ: CWE-79
- OWASP Top 10:2025 — A05:2025 Injection
हमले का चरण-दर-चरण प्रवाह
इनपुट रिक्वेस्ट में आता है
सर्च बॉक्स, प्रोफ़ाइल फ़ॉर्म या लिंक पैरामीटर सर्वर को q, bio या next भेजता है। पीड़ित को तैयार किया हुआ लिंक भेजा जा सकता है, या मान सेव होकर प्रोफ़ाइल देखने वाले हर व्यक्ति को दिखता है।
मान एस्केपिंग से बच निकलता है
f-स्ट्रिंग उसे टेम्पलेट सोर्स में रख देती है, Markup() या mark_safe() उसे भरोसेमंद बता देते हैं, या वह href में पहुँचता है, जहाँ < और > एस्केप करना काफ़ी नहीं। हानिरहित टेस्ट मान <img src=x onerror=alert(1)> बिना बदले निकल जाता है।
ब्राउज़र उसे मार्कअप की तरह पढ़ता है
रिस्पॉन्स में <img src=x onerror=alert(1)> असली एलिमेंट के रूप में होता है। अगर इमेज resource लोड नहीं होता, तो onerror चलता है, और लिंक में javascript: URL क्लिक करने पर चलता है।
स्क्रिप्ट वेबसाइट बनकर चलती है
दिखाए गए Flask/Jinja और Django paths में निकला markup application origin पर चलता है। Untrusted values को autoescaped template data positions में रखें; HTML केवल context-appropriate sanitization के बाद स्वीकारें।
सोर्स कोड: कमज़ोर बनाम सुरक्षित कार्यान्वयन
# views.py: नीचे का हर हैंडलर रिक्वेस्ट टेक्स्ट को HTML बनकर पेज में जाने देता है
from django.utils.safestring import mark_safe
from flask import Flask, render_template_string, request
from markupsafe import Markup
app = Flask(__name__)
# f-स्ट्रिंग टेम्पलेट सोर्स बनाती है, इसलिए Jinja2 q को कभी एस्केप नहीं करता
@app.get("/search")
def search():
q = request.args.get("q", "")
return render_template_string(f"<h1>Results for {q}</h1>")
# Markup() Jinja2 से कहता है कि bio भरोसेमंद HTML है
@app.get("/profile")
def profile():
bio = request.args.get("bio", "")
return render_template_string("<div class='bio'>{{ bio }}</div>", bio=Markup(bio))
# f-स्ट्रिंग पर mark_safe name को HTML के रूप में छाप देता है
def greeting(name):
return mark_safe(f"<p>Hello, {name}</p>")
# autoescape href में javascript: URL को नहीं रोकता
@app.get("/continue")
def continue_link():
url = request.args.get("next", "/")
return render_template_string('<a href="{{ url }}">Continue</a>', url=url)
# views.py: मान टेम्पलेट वेरिएबल रहते हैं; रिच टेक्स्ट सैनिटाइज़ होता है
import nh3
from django.utils.html import format_html
from flask import Flask, render_template_string, request
app = Flask(__name__)
# q टेम्पलेट वेरिएबल है, इसलिए autoescape उसे एनकोड करता है
@app.get("/search")
def search():
q = request.args.get("q", "")
return render_template_string("<h1>Results for {{ q }}</h1>", q=q)
# HTML पर भरोसा करने से पहले nh3 टैग अनुमति-सूची लागू करता है
@app.get("/profile")
def profile():
bio = request.args.get("bio", "")
return render_template_string("<div class='bio'>{{ bio|safe }}</div>", bio=nh3.clean(bio))
# format_html नतीजे को सुरक्षित चिह्नित करने से पहले हर आर्ग्युमेंट एस्केप करता है
def greeting(name):
return format_html("<p>Hello, {}</p>", name)
# लिंक के लक्ष्य के रूप में सिर्फ़ अपनी साइट के पाथ मान्य हैं
@app.get("/continue")
def continue_link():
url = request.args.get("next", "/")
if not url.startswith("/") or url.startswith("//"):
url = "/"
return render_template_string('<a href="{{ url }}">Continue</a>', url=url)
इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट
- ✓Bandit B703/B308 (
mark_safe) और B704 (non-literal परMarkup) पर, और ऐसे हरrender_template_stringपर CI फ़ेल करें जिसका पहला आर्ग्युमेंट f-स्ट्रिंग,%या.format()का नतीजा हो। - ✓मानों को टेम्पलेट में वेरिएबल की तरह दें और autoescape को काम करने दें; अलग Jinja2 के लिए
Environment(autoescape=select_autoescape())बनाएँ। - ✓छोटे टुकड़ों के लिए
mark_safe()के बजायformat_html(), और यूज़र के हर HTML के लिएnh3.clean()इस्तेमाल करें। - ✓यूज़र के नियंत्रण वाले हर URL को
hrefयाsrcतक पहुँचने से पहले जाँचें: सिर्फ़ एक/से शुरू होने वाले अपनी साइट के पाथ, या स्पष्ट http/https अनुमति-सूची स्वीकार करें। - ✓nonce-आधारित
script-srcवाली और'unsafe-inline'के बिना Content-Security-Policy भेजें, और सेशन cookies कोHttpOnlyवSameSite=Laxरखें।
स्रोत
- CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
- Flask — Security Considerations: Cross-Site Scripting
- Django — Security: Cross site scripting (XSS) protection
- Django — format_html
- nh3 — Python bindings to the ammonia HTML sanitizer
- OWASP Cross Site Scripting Prevention Cheat Sheet
- nh3 — Python bindings to the ammonia HTML sanitizer