FlawOpen

flawopen.com/xss/Java

● CWE-79 · Tinggi
Riset Keamanan · FlawOpen

XSS Java: amankan output Spring MVC dan Thymeleaf

th:utext dan JSP tanpa escape dapat menjalankan JavaScript. Lakukan encoding sesuai konteks dan sanitasi HTML dengan allowlist (CWE-79).

💡 Penjelasan Sederhana (ELI5)

Papan skor stadion memiliki kolom nama biasa dan mode tampilan kaya yang terpisah. th:text milik Thymeleaf memperlakukan nama kiriman sebagai teks literal; th:utext meminta renderer memperlakukan nilainya sebagai instruksi HTML untuk papan. Browser menerima text/html dari situs stadion dan menampilkan markup dengan otoritas situs tersebut. Pertahankan teks pengguna pada keluaran yang di-escape, lalu sanitasi sebelum memakai slot template tanpa escape jika format kaya memang diperlukan.

Konsep Kunci & Istilah

Thymeleaf th:text vs th:utext
th:text meng-escape nilainya sebagai HTML; th:utext (teks tanpa escape) menyisipkannya sebagai markup. Dalam bentuk inline Thymeleaf, [[${x}]] meng-escape dan [(${x})] tidak. th:utext yang terikat ke input pengguna adalah XSS kecuali nilainya sudah disanitasi lebih dulu.
JSP ${} vs <c:out>
Di teks template JSP, ${param.q} ditulis tanpa escape. <c:out value="${param.q}"/> atau ${fn:escapeXml(param.q)} meng-escape-nya. Scriptlet seperti <%= request.getParameter("q") %> tidak pernah meng-escape.
Encode.forHtml / forJavaScript
OWASP Java Encoder punya satu metode per konteks output: Encode.forHtml untuk isi elemen, forHtmlAttribute untuk atribut berkutip, forJavaScript untuk literal string JavaScript, dan forUriComponent untuk parameter URL. Meng-encode HTML untuk nilai yang ditaruh di dalam skrip tidak cukup.
OWASP Java HTML Sanitizer
Untuk HTML dari pengguna, policy seperti Sanitizers.FORMATTING.and(Sanitizers.LINKS) memegang allowlist tag dan atribut serta membuang skrip, event handler, dan URL javascript:. Encoding akan menampilkan tag sebagai teks; sanitasi membuat tag yang aman tetap berfungsi.
@ResponseBody returning text/html
Metode @RestController yang mengembalikan String dengan produces = text/html ditulis ke respons apa adanya: tidak ada mesin template yang berjalan, jadi tidak ada yang di-escape. Utamakan template view, atau encode setiap nilai yang digabungkan.

Pemetaan kerangka kerja

Alur Serangan Langkah demi Langkah

Langkah 1

Input datang sebagai parameter request

Spring mengikat q, user, atau bio dari query string atau body formulir ke argumen String. Korban bisa dikirimi tautan yang sudah dirancang, atau bio yang dikirim ditampilkan ke pengguna lain.

Langkah 2

Penggabungan string menyusun respons

Controller menempelkan nilai ke HTML atau ke blok <script>. Dengan nilai uji yang tidak berbahaya <img src=x onerror=alert(1)>, string yang dikembalikan berisi tag sungguhan; dengan ';alert(1);//, blok skrip mendapat perintah baru.

Langkah 3

Browser menguraikannya

Karena metode menyatakan text/html, browser merender body sebagai halaman. Gambar gagal dan memicu onerror, atau blok skrip menjalankan perintah yang disisipkan.

Langkah 4

Skrip berjalan atas nama situs

Kode dijalankan dengan origin aplikasi: ia bisa membaca DOM, memanggil endpoint aplikasi dengan cookie sesi korban, dan mengubah apa yang dilihat pengguna.

Kode Sumber: Rentan vs Aman

IMPLEMENTASI RENTAN
// ProfileController.java: setiap endpoint menggabungkan data request ke HTML
@RestController
public class ProfileController {

    // konteks HTML: nilai query disisipkan sebagai markup
    @GetMapping(value = "/search", produces = MediaType.TEXT_HTML_VALUE)
    public String search(@RequestParam String q) {
        return "<h1>Results for " + q + "</h1>";
    }

    // konteks JavaScript: tanda kutip di user mengakhiri literal string
    @GetMapping(value = "/widget", produces = MediaType.TEXT_HTML_VALUE)
    public String widget(@RequestParam String user) {
        return "<script>const user = '" + user + "';</script>";
    }

    // teks kaya dikembalikan lengkap dengan semua tag dan handler
    @PostMapping(value = "/preview", produces = MediaType.TEXT_HTML_VALUE)
    public String preview(@RequestParam String bio) {
        return "<div class=\"bio\">" + bio + "</div>";
    }
}
PERBAIKAN AMAN & KUAT
// ProfileController.java: setiap nilai di-encode untuk konteksnya atau disanitasi
@RestController
public class ProfileController {

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

    // forHtml meng-encode < > & " ' untuk isi elemen
    @GetMapping(value = "/search", produces = MediaType.TEXT_HTML_VALUE)
    public String search(@RequestParam String q) {
        return "<h1>Results for " + Encode.forHtml(q) + "</h1>";
    }

    // forJavaScript meng-escape tanda kutip, backslash, dan urutan </script>
    @GetMapping(value = "/widget", produces = MediaType.TEXT_HTML_VALUE)
    public String widget(@RequestParam String user) {
        return "<script>const user = '" + Encode.forJavaScript(user) + "';</script>";
    }

    // sanitizer mempertahankan format dan tautan lalu membuang sisanya
    @PostMapping(value = "/preview", produces = MediaType.TEXT_HTML_VALUE)
    public String preview(@RequestParam String bio) {
        return "<div class=\"bio\">" + BIO_POLICY.sanitize(bio) + "</div>";
    }
}

Daftar Periksa Penguatan Sistem Rekayasa

Sumber