FlawOpen

flawopen.com/xss/Java

● CWE-79 · उच्च
सुरक्षा अनुसंधान · FlawOpen

Java XSS: Spring MVC और Thymeleaf आउटपुट सुरक्षित करें

th:utext और बिना escape JSP JavaScript चला सकते हैं। संदर्भ के अनुसार encode करें और HTML को allow-list से साफ़ करें (CWE-79)।

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

स्टेडियम के scoreboard में सामान्य नाम का field और rich display का अलग mode होता है। Thymeleaf का th:text दिए गए नाम को अक्षरशः text मानता है; th:utext renderer से उस value को बोर्ड के HTML निर्देश की तरह लेने को कहता है। Browser स्टेडियम साइट का text/html पाता है और उस markup को साइट के अधिकार से render करता है। User text को escaped output में रखें; rich formatting की आवश्यकता हो तो unescaped template slot में देने से पहले sanitize करें।

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

Thymeleaf th:text vs th:utext
th:text अपने मान को HTML-एस्केप करता है; th:utext (बिना एस्केप का टेक्स्ट) उसे मार्कअप की तरह डालता है। Thymeleaf के inline रूप में [[${x}]] एस्केप करता है और [(${x})] नहीं। यूज़र इनपुट से जुड़ा th:utext XSS है, जब तक मान पहले सैनिटाइज़ न हुआ हो।
JSP ${} vs <c:out>
JSP टेम्पलेट टेक्स्ट में ${param.q} बिना एस्केप के लिखा जाता है। <c:out value="${param.q}"/> या ${fn:escapeXml(param.q)} उसे एस्केप करते हैं। <%= request.getParameter("q") %> जैसे scriptlet कभी एस्केप नहीं करते।
Encode.forHtml / forJavaScript
OWASP Java Encoder में हर आउटपुट संदर्भ के लिए एक मेथड है: element सामग्री के लिए Encode.forHtml, कोट वाले एट्रिब्यूट के लिए forHtmlAttribute, JavaScript string literal के लिए forJavaScript और URL पैरामीटर के लिए forUriComponent। स्क्रिप्ट के अंदर रखे मान को सिर्फ़ HTML-एनकोड करना काफ़ी नहीं है।
OWASP Java HTML Sanitizer
यूज़र के दिए HTML के लिए Sanitizers.FORMATTING.and(Sanitizers.LINKS) जैसी policy टैग और एट्रिब्यूट की अनुमति-सूची रखती है और स्क्रिप्ट, इवेंट हैंडलर व javascript: URL हटा देती है। एनकोडिंग टैग को टेक्स्ट की तरह दिखाती; सैनिटाइज़ करना सुरक्षित टैग को काम करने देता है।
@ResponseBody returning text/html
produces = text/html के साथ String लौटाने वाला @RestController मेथड रिस्पॉन्स में जैसा है वैसा लिखा जाता है: कोई टेम्पलेट इंजन नहीं चलता, इसलिए कुछ भी एस्केप नहीं होता। view टेम्पलेट इस्तेमाल करें, या जोड़े गए हर मान को एनकोड करें।

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

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

चरण 1

इनपुट रिक्वेस्ट पैरामीटर में आता है

Spring query string या फ़ॉर्म body से q, user या bio को String आर्ग्युमेंट से बाँधता है। पीड़ित को तैयार किया लिंक भेजा जा सकता है, या भेजी गई bio दूसरे यूज़र्स को दिखती है।

चरण 2

स्ट्रिंग जोड़कर रिस्पॉन्स बनता है

controller मान को HTML या <script> ब्लॉक में चिपका देता है। हानिरहित टेस्ट मान <img src=x onerror=alert(1)> के साथ लौटाई स्ट्रिंग में असली टैग होता है; ';alert(1);// के साथ स्क्रिप्ट ब्लॉक में नया स्टेटमेंट जुड़ जाता है।

चरण 3

ब्राउज़र उसे पार्स करता है

क्योंकि मेथड text/html घोषित करता है, ब्राउज़र body को पेज की तरह रेंडर करता है। इमेज फ़ेल होकर onerror चलाती है, या स्क्रिप्ट ब्लॉक डाला गया स्टेटमेंट चला देता है।

चरण 4

स्क्रिप्ट वेबसाइट बनकर चलती है

कोड ऐप्लिकेशन के origin के साथ चलता है: DOM पढ़ सकता है, पीड़ित की सेशन cookie के साथ ऐप्लिकेशन के endpoint बुला सकता है और यूज़र को दिखने वाली चीज़ें बदल सकता है।

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

कमज़ोर कार्यान्वयन
// ProfileController.java: हर endpoint रिक्वेस्ट डेटा को HTML में जोड़ता है
@RestController
public class ProfileController {

    // HTML संदर्भ: query का मान मार्कअप बनकर डलता है
    @GetMapping(value = "/search", produces = MediaType.TEXT_HTML_VALUE)
    public String search(@RequestParam String q) {
        return "<h1>Results for " + q + "</h1>";
    }

    // JavaScript संदर्भ: user में कोट string literal को ख़त्म कर देता है
    @GetMapping(value = "/widget", produces = MediaType.TEXT_HTML_VALUE)
    public String widget(@RequestParam String user) {
        return "<script>const user = '" + user + "';</script>";
    }

    // रिच टेक्स्ट सारे टैग और हैंडलर समेत लौट आता है
    @PostMapping(value = "/preview", produces = MediaType.TEXT_HTML_VALUE)
    public String preview(@RequestParam String bio) {
        return "<div class=\"bio\">" + bio + "</div>";
    }
}
सुरक्षित और सुदृढ़ फ़िक्स
// ProfileController.java: हर मान अपने संदर्भ के लिए एनकोड या सैनिटाइज़ होता है
@RestController
public class ProfileController {

    private static final PolicyFactory BIO_POLICY = Sanitizers.FORMATTING.and(Sanitizers.LINKS);

    // forHtml element सामग्री के लिए < > & " ' एनकोड करता है
    @GetMapping(value = "/search", produces = MediaType.TEXT_HTML_VALUE)
    public String search(@RequestParam String q) {
        return "<h1>Results for " + Encode.forHtml(q) + "</h1>";
    }

    // forJavaScript कोट, बैकस्लैश और </script> क्रम एस्केप करता है
    @GetMapping(value = "/widget", produces = MediaType.TEXT_HTML_VALUE)
    public String widget(@RequestParam String user) {
        return "<script>const user = '" + Encode.forJavaScript(user) + "';</script>";
    }

    // सैनिटाइज़र फ़ॉर्मेटिंग और लिंक रखता है, बाक़ी सब हटा देता है
    @PostMapping(value = "/preview", produces = MediaType.TEXT_HTML_VALUE)
    public String preview(@RequestParam String bio) {
        return "<div class=\"bio\">" + BIO_POLICY.sanitize(bio) + "</div>";
    }
}

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

स्रोत