flawopen.com/xss/Java
Java-XSS: Spring MVC und Thymeleaf absichern
th:utext und JSP ohne Escaping können JavaScript ausführen. Escapen Sie kontextbezogen und bereinigen Sie HTML per Allowlist (CWE-79).
Die Anzeigetafel eines Stadions hat ein einfaches Namensfeld und einen getrennten Modus für formatierte Inhalte. Thymeleafs th:text behandelt einen eingegebenen Namen als wörtlichen Text; th:utext weist den Renderer an, den Wert als HTML-Anweisungen für die Tafel zu behandeln. Der Browser erhält das text/html der Stadionwebsite und zeigt das Markup mit deren Autorität an. Lassen Sie Nutzereingaben im maskierten Ausgabepfad und bereinigen Sie sie vor einem unmaskierten Template-Platzhalter, wenn Formatierung nötig ist.
Kernkonzepte & Begriffe
Thymeleaf th:text vs th:utextth:textmaskiert seinen Wert als HTML;th:utext(unmaskierter Text) fügt ihn als Markup ein. In Thymeleafs Inline-Form maskiert[[${x}]],[(${x})]nicht. Ein an Nutzereingaben gebundenesth:utextist XSS, sofern der Wert nicht vorher bereinigt wurde.JSP ${} vs <c:out>- Im JSP-Template-Text wird
${param.q}ohne Maskierung ausgegeben.<c:out value="${param.q}"/>oder${fn:escapeXml(param.q)}maskieren ihn. Scriptlets wie<%= request.getParameter("q") %>maskieren nie. Encode.forHtml / forJavaScript- Der OWASP Java Encoder hat eine Methode pro Ausgabekontext:
Encode.forHtmlfür Elementinhalt,forHtmlAttributefür quotierte Attribute,forJavaScriptfür JavaScript-String-Literale undforUriComponentfür URL-Parameter. Einen Wert in einem Skript nur HTML-zu-kodieren, reicht nicht. OWASP Java HTML Sanitizer- Für nutzergeliefertes HTML hält eine Policy wie
Sanitizers.FORMATTING.and(Sanitizers.LINKS)eine Allowlist von Tags und Attributen und entfernt Skripte, Event-Handler undjavascript:-URLs. Kodieren würde die Tags als Text zeigen; Bereinigen lässt die sicheren funktionieren. @ResponseBody returning text/html- Eine
@RestController-Methode, die einenStringmitproduces = text/htmlzurückgibt, wird unverändert in die Antwort geschrieben: Keine Template-Engine läuft, also wird nichts maskiert. Besser ein View-Template verwenden oder jeden verketteten Wert kodieren.
Framework-Zuordnungen
- CWE-Referenz für die in diesem Leitfaden beschriebene Schwachstelle: CWE-79
- OWASP Top 10:2025 — A05:2025 Injection
Schritt-für-Schritt Angriffsablauf
Die Eingabe kommt als Request-Parameter
Spring bindet q, user oder bio aus Query-String oder Formular-Body an ein String-Argument. Dem Opfer kann ein präparierter Link geschickt werden, oder eine eingereichte Bio wird anderen Nutzern angezeigt.
Verkettung baut die Antwort
Der Controller klebt den Wert ins HTML oder in einen <script>-Block. Mit dem harmlosen Testwert <img src=x onerror=alert(1)> enthält der zurückgegebene String ein echtes Tag; mit ';alert(1);// bekommt der Skriptblock eine neue Anweisung.
Der Browser parst sie
Weil die Methode text/html deklariert, rendert der Browser den Body als Seite. Das Bild schlägt fehl und löst onerror aus, oder der Skriptblock führt die eingeschleuste Anweisung aus.
Das Skript läuft im Namen der Website
Der Code läuft mit dem Origin der Anwendung: Er kann das DOM lesen, die Endpunkte der Anwendung mit dem Session-Cookie des Opfers aufrufen und ändern, was der Nutzer sieht.
Quellcode: Verwundbar vs. Sicher
// ProfileController.java: jeder Endpunkt verkettet Request-Daten ins HTML
@RestController
public class ProfileController {
// HTML-Kontext: der Query-Wert wird als Markup eingefügt
@GetMapping(value = "/search", produces = MediaType.TEXT_HTML_VALUE)
public String search(@RequestParam String q) {
return "<h1>Results for " + q + "</h1>";
}
// JavaScript-Kontext: ein Anführungszeichen in user beendet das String-Literal
@GetMapping(value = "/widget", produces = MediaType.TEXT_HTML_VALUE)
public String widget(@RequestParam String user) {
return "<script>const user = '" + user + "';</script>";
}
// Rich Text kommt mit allen Tags und Handlern zurück
@PostMapping(value = "/preview", produces = MediaType.TEXT_HTML_VALUE)
public String preview(@RequestParam String bio) {
return "<div class=\"bio\">" + bio + "</div>";
}
}
// ProfileController.java: jeder Wert wird für seinen Kontext kodiert oder bereinigt
@RestController
public class ProfileController {
private static final PolicyFactory BIO_POLICY = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
// forHtml kodiert < > & " ' für Elementinhalt
@GetMapping(value = "/search", produces = MediaType.TEXT_HTML_VALUE)
public String search(@RequestParam String q) {
return "<h1>Results for " + Encode.forHtml(q) + "</h1>";
}
// forJavaScript maskiert Anführungszeichen, Backslashes und </script>-Folgen
@GetMapping(value = "/widget", produces = MediaType.TEXT_HTML_VALUE)
public String widget(@RequestParam String user) {
return "<script>const user = '" + Encode.forJavaScript(user) + "';</script>";
}
// der Sanitizer behält Formatierung und Links und verwirft alles andere
@PostMapping(value = "/preview", produces = MediaType.TEXT_HTML_VALUE)
public String preview(@RequestParam String bio) {
return "<div class=\"bio\">" + BIO_POLICY.sanitize(bio) + "</div>";
}
}
Checkliste für Engineering & Systemsicherheit
- ✓Find Security Bugs in SpotBugs aktivieren und den Build bei
XSS_SERVLET,XSS_JSP_PRINTundXSS_REQUEST_PARAMETER_TO_SERVLET_WRITERscheitern lassen. - ✓Templates im CI nach
th:utext,[(,<%=und nackten${...}in JSP durchsuchen; jeder Treffer muss bereinigten oder konstanten Inhalt rendern. - ✓Mit dem OWASP Java Encoder (
forHtml,forHtmlAttribute,forJavaScript,forUriComponent) für den genauen Kontext kodieren, wann immer ein Controller selbst Markup baut. - ✓Nutzergeliefertes HTML durch eine OWASP-Java-HTML-Sanitizer-Policy schicken, bevor es
th:utextoder einen Response-Body erreicht. - ✓Spring Securitys
headers().contentSecurityPolicy(...)mit Nonce-basiertemscript-srckonfigurieren und das Session-CookieHttpOnlyundSameSite=Laxhalten.