flawopen.com/xss/Javascript
JavaScript XSS: DOM, React और Express सुरक्षित करें
असुरक्षित DOM/React sinks और links JavaScript चला सकते हैं। Text APIs अपनाएँ, HTML साफ़ करें, URL जाँचें और Trusted Types लागू करें (CWE-79)।
एक थिएटर के cue board की कल्पना करें। यदि stage manager दर्शकों की हर पर्ची को मंच-निर्देश माने, तो पर्ची अनपेक्षित दृश्य जोड़ सकती है; सामान्य संवाद की तरह दिखाने पर वह केवल पाठ रहती है। Browser में innerHTML और React का dangerouslySetInnerHTML DOM से दिए गए पाठ को markup की तरह parse करवाते हैं, जबकि textContent उसे अक्षरशः दिखाता है। सामान्य पाठ escape करें, केवल जानबूझकर अनुमति दिए HTML को sanitize करें, और link बनाने से पहले URL schemes जाँचें।
इस पेज के मुख्य शब्द
innerHTML vs textContentinnerHTML,outerHTMLयाinsertAdjacentHTMLमें मान देने से ब्राउज़र स्ट्रिंग को HTML की तरह पार्स करता है, इसलिए उसमें मौजूद<img onerror>एक सक्रिय एलिमेंट बन जाता है।textContentऔरcreateTextNodeसिर्फ़ टेक्स्ट डालते हैं; मान का कोई हिस्सा कभी मार्कअप की तरह पार्स नहीं होता।Template literals in res.send()- Express का
res.send()स्ट्रिंग को डिफ़ॉल्ट रूप सेtext/htmlके रूप में भेजता है। template literal भेजने से पहले${req.query.q}हल कर देता है, इसलिए रिस्पॉन्स में वही होता है जो यूज़र ने query string में डाला।req.query.qarray या object भी हो सकता है, इसलिए टाइप जाँचें। dangerouslySetInnerHTML + DOMPurify- React JSX में टेक्स्ट को अपने-आप एस्केप करता है;
dangerouslySetInnerHTMLइससे स्पष्ट छूट है। मान कोDOMPurify.sanitize()से गुज़ारें, जो उसे ब्राउज़र के अपने पार्सर से पढ़ता है और React के डालने से पहले स्क्रिप्ट, इवेंट हैंडलर और ख़तरनाक URL हटा देता है। javascript: URLs in href- JSX एट्रिब्यूट का मान एस्केप करता है, लेकिन
href="javascript:..."एक वैध URL है जो क्लिक करने पर कोड चलाता है।new URL()से पार्स करें और सिर्फ़http:वhttps:की अनुमति दें; URL पार्सर शुरुआती स्पेस और टैब हटा देता है जिन्हें साधारण prefix जाँच चूक जाती है। Trusted Types- CSP डायरेक्टिव
require-trusted-types-for 'script'के साथ समर्थित ब्राउज़रinnerHTMLजैसे sink में साधारण स्ट्रिंग ठुकरा देते हैं और सिर्फ़ आपकी बनाई policy (आम तौर पर DOMPurify बुलाने वाली) से बनेTrustedHTMLobject स्वीकार करते हैं। नए sink चुपचाप नहीं जुड़ पाते।
फ्रेमवर्क मैपिंग
- इस गाइड में बताई गई कमजोरी के लिए CWE संदर्भ: CWE-79
- OWASP Top 10:2025 — A05:2025 Injection
हमले का चरण-दर-चरण प्रवाह
इनपुट किसी sink तक पहुँचता है
सर्च क्वेरी, API से आया प्रोफ़ाइल फ़ील्ड या किसी दूसरे यूज़र का सेव किया लिंक स्ट्रिंग बनकर आता है और innerHTML, dangerouslySetInnerHTML, href या res.send() को दिया जाता है।
मान मार्कअप बन जाता है
हानिरहित टेस्ट मान <img src=x onerror=alert(1)> के साथ innerHTML और res.send() असली <img> एलिमेंट बनाते हैं; सेव किया javascript:alert(1) लिंक क्लिक करने लायक़ स्क्रिप्ट बन जाता है।
ब्राउज़र उसे चला देता है
इमेज लोड नहीं होती और onerror चलता है, या यूज़र लिंक पर क्लिक करता है। हमलावर के कुछ और किए बिना कोड तुरंत चलता है।
स्क्रिप्ट वेबसाइट बनकर काम करती है
यह पेज के origin के साथ चलती है: DOM पढ़ सकती है, पीड़ित की cookies या localStorage के टोकन से साइट के API बुला सकती है और यूज़र को दिखने वाली चीज़ें बदल सकती है।
सोर्स कोड: कमज़ोर बनाम सुरक्षित कार्यान्वयन
// profile.jsx: नीचे का हर फ़ंक्शन किसी स्ट्रिंग को HTML की तरह पार्स होने देता है
import express from 'express';
const app = express();
// Express: भेजने से पहले query का मान HTML में डाल दिया जाता है
app.get('/search', (req, res) => {
res.send(`<h1>Results for ${req.query.q}</h1>`);
});
// innerHTML name को मार्कअप की तरह पार्स करता है
export function showGreeting(el, name) {
el.innerHTML = `Hello, ${name}`;
}
// React HTML को ठीक वैसा ही डाल देता है जैसा मिला
export function Bio({ html }) {
return <div className="bio" dangerouslySetInnerHTML={{ __html: html }} />;
}
// लिंक पर क्लिक करते ही javascript: URL चलता है
export function Website({ url }) {
return <a href={url}>Website</a>;
}
// profile.jsx: टेक्स्ट टेक्स्ट ही रहता है; HTML और URL पहले जाँचे जाते हैं
import DOMPurify from 'dompurify';
import escapeHtml from 'escape-html';
import express from 'express';
const app = express();
// सिर्फ़ स्ट्रिंग स्वीकार होती हैं, और उनका HTML एस्केप होता है
app.get('/search', (req, res) => {
const q = typeof req.query.q === 'string' ? req.query.q : '';
res.send(`<h1>Results for ${escapeHtml(q)}</h1>`);
});
// textContent कभी मार्कअप पार्स नहीं करता
export function showGreeting(el, name) {
el.textContent = `Hello, ${name}`;
}
// डालने से पहले DOMPurify स्क्रिप्ट और इवेंट हैंडलर हटा देता है
export function Bio({ html }) {
return <div className="bio" dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />;
}
// सिर्फ़ http और https URL बचते हैं; बाक़ी सब # बन जाता है
function safeHref(url) {
try {
const parsed = new URL(url, window.location.origin);
return ['http:', 'https:'].includes(parsed.protocol) ? parsed.href : '#';
} catch {
return '#';
}
}
export function Website({ url }) {
return <a href={safeHref(url)}>Website</a>;
}
इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट
- ✓CI में
react/no-dangerऔरeslint-plugin-no-unsanitizedचालू करें, औरinnerHTML,outerHTML,insertAdjacentHTMLवdocument.writeमें non-literal मान रोकें। - ✓टेक्स्ट के लिए
textContentइस्तेमाल करें; जो HTML दिखाना ज़रूरी हो उसे डालने की जगह परDOMPurify.sanitize()से गुज़ारें। - ✓यूज़र के दिए हर URL को
href,srcयाwindow.locationतक पहुँचने से पहलेnew URL()औरhttp:/https:अनुमति-सूची से जाँचें। - ✓Express में अपने-आप एस्केप करने वाले view engine या
escape-htmlसे रेंडर करें, और स्ट्रिंग न होने वाले query पैरामीटर ठुकराएँ। - ✓nonce-आधारित
script-srcवाली,'unsafe-inline'के बिना औरrequire-trusted-types-for 'script'वाली CSP भेजें; सेशन टोकनlocalStorageके बजायHttpOnlycookies में रखें।