flawopen.com/Cross-Site Scripting/Java
Imagine a guestbook where visitors write a public note. Stored XSS is like someone writing a note that isn't just text — it's a hidden trick that makes the guestbook page itself start doing things, like stealing the next visitor's login session, the moment anyone opens the page to read it.
Cross-site scripting happens when untrusted input reaches the rendered page without proper output encoding. In Java web apps this shows up in JSP scriptlets that print a value raw, and in template engines like Thymeleaf when their explicit "unescaped" output mode is used for untrusted data.
In 2005, an 19-year-old user exploited a stored XSS flaw on MySpace to create the "Samy" worm — a script that added itself to every profile that viewed it, infecting over a million profiles within about 20 hours and forcing MySpace offline to contain it. It remains one of the most-cited demonstrations of how quickly stored XSS can self-propagate.
Source: widely documented in security industry retrospectives — see References below.<!-- JSP scriptlet, no escaping -->
<div>
Welcome, <%= request.getParameter("name") %>
</div>
<!-- JSTL c:out escapes by default -->
<div>
Welcome, <c:out value="${param.name}"/>
</div>
<c:out> HTML-encodes its value by default before writing it to the response — special characters become their entity equivalents, so a <script> tag in the parameter renders as visible text. A raw JSP scriptlet expression (<%= %>) writes exactly what it's given, with no encoding at all.
th:text escapes by default; th:utext is the explicit, named escape hatch for when you genuinely need to render real HTML — using it for untrusted data reopens the exact same hole as a raw JSP expression.
<%= someValue %> writes its value directly to the response with zero encoding — it's the JSP equivalent of building HTML with string concatenation, and it's still common in legacy codebases.
A controller method that builds and returns an HTML string directly (rather than rendering a template) gets none of the template engine's default escaping — the responsibility shifts entirely to manual encoding.
True for th:text and c:out — false for th:utext or a raw scriptlet expression.
Any reflected value counts — a search query echoed on a results page, a URL parameter shown in an error message, a filename displayed after upload.
grep -rn "th:utext" --include="*.html" .
grep -rn "<%=" --include="*.jsp" .
Same underlying shape — untrusted data mixed into a command's structure without encoding — but the target and damage differ: SQL injection targets the database, XSS targets other users' browsers.
Spring Security can add security headers like Content-Security-Policy as a second layer of defense, but it doesn't perform output encoding for you — that's still the template engine's job.