How to Prevent XSS Attacks in JSP/Servlet Web Applications: A Comprehensive Guide

Cross-Site Scripting (XSS) is one of the most prevalent web security vulnerabilities, affecting millions of applications worldwide. It occurs when an attacker injects malicious scripts (typically JavaScript) into web pages viewed by other users. For Java developers building JSP/Servlet applications, understanding and mitigating XSS is critical—these technologies dynamically generate HTML, making them prime targets for injection attacks if not secured properly.

In this guide, we’ll demystify XSS, explore how it impacts JSP/Servlet apps, and provide actionable strategies to prevent it. Whether you’re a beginner or an experienced developer, this article will equip you with the tools to harden your application against XSS threats.

Table of Contents#

  1. What is Cross-Site Scripting (XSS)?
  2. Types of XSS Attacks
  3. How XSS Affects JSP/Servlet Applications
  4. Key Prevention Techniques
  5. Example Scenarios: Vulnerable vs. Secure Code
  6. Conclusion
  7. References

What is Cross-Site Scripting (XSS)?#

XSS is a web security flaw that allows attackers to inject malicious scripts into content delivered to end-users. When a victim visits the compromised page, their browser executes the injected script, giving the attacker access to sensitive data (e.g., cookies, session tokens), or enabling actions like impersonation, defacement, or malware distribution.

Why is XSS dangerous?
Browsers trust scripts from the same origin as the web page. An injected script runs in the victim’s browser with the same privileges as the legitimate application, bypassing same-origin policies.

Types of XSS Attacks#

XSS attacks are categorized based on how the malicious script is stored and executed:

Stored XSS (Persistent XSS)#

The attacker’s script is permanently stored on the server (e.g., in a database, comment field, or user profile). When other users access the page, the server retrieves the stored data and renders it, executing the script.

Example in JSP/Servlets: A user submits a comment with <script>stealCookies()</script>. The Servlet stores this in a database. When the JSP displays all comments, it unknowingly renders the script, attacking every visitor.

Reflected XSS (Non-Persistent XSS)#

The attacker’s script is not stored on the server. Instead, it is included in a URL or request parameter. When the victim clicks the malicious link, the server “reflects” the script back in the response, executing it in the victim’s browser.

Example: A search Servlet takes a query parameter and directly outputs it in the JSP:
http://example.com/search?query=<script>alert('XSS')</script>. The JSP renders the unescaped query, triggering the script.

DOM-Based XSS#

The attack occurs client-side without involving the server. The malicious script manipulates the Document Object Model (DOM) of the page using client-side JavaScript. The server returns safe data, but the client-side code unsafely processes user input (e.g., from location.search or document.cookie).

Example: A JSP includes JavaScript that reads a URL parameter and inserts it into the DOM with innerHTML:
var userInput = new URLSearchParams(window.location.search).get('name'); document.getElementById('greeting').innerHTML = userInput;. An attacker crafts a URL with name=<script>...</script>, which the client-side code executes.

How XSS Affects JSP/Servlet Applications#

JSP/Servlet applications dynamically generate HTML by combining server-side logic (Servlets) with presentation (JSPs). This dynamic nature creates XSS risks if:

  • User input is not validated before storage or processing.
  • Output is not encoded when rendered in HTML, JavaScript, or other contexts.
  • Dangerous practices like scriptlets (<% %>) or unescaped Expression Language (EL) are used in JSPs.

Common entry points for XSS in JSP/Servlets include: form inputs, URL parameters, cookies, HTTP headers, and file uploads.

Key Prevention Techniques#

Preventing XSS requires a defense-in-depth approach. No single technique is sufficient—combine input validation, output encoding, secure coding practices, and runtime protections.

1. Input Validation: Sanitize All User Inputs#

Validate all user inputs (form data, URL parameters, headers) on the server side (client-side validation is easily bypassed). Use whitelists (allow only known-safe characters) instead of blacklists (block known-bad characters, which attackers can bypass).

Best Practices:#

  • Use regular expressions to restrict input to allowed characters (e.g., alphanumerics, spaces, and basic punctuation).
    Example: Validate a username with ^[a-zA-Z0-9_]{3,20}$ (3-20 characters, letters, numbers, underscores).
  • Leverage validation frameworks like Hibernate Validator or Spring Validation for declarative rules (e.g., @Pattern(regexp = "^[a-zA-Z0-9_]+$")).
  • Reject or sanitize inputs with unexpected characters (e.g., <, >, &, ", ').

Example: Validating a Parameter in a Servlet

import java.util.regex.Pattern;
 
@WebServlet("/search")
public class SearchServlet extends HttpServlet {
    private static final Pattern SAFE_QUERY_PATTERN = Pattern.compile("^[a-zA-Z0-9\\s.,!?'-]{1,100}$");
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String query = request.getParameter("query");
        
        // Validate input
        if (query == null || !SAFE_QUERY_PATTERN.matcher(query).matches()) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid search query.");
            return;
        }
        
        // Proceed with valid query...
        request.setAttribute("query", query);
        request.getRequestDispatcher("/search.jsp").forward(request, response);
    }
}

2. Output Encoding: Escape Data Before Rendering#

Output encoding is the most critical defense against XSS. Even validated input must be encoded when rendered, as context (HTML, JavaScript, URLs) determines how data is interpreted by browsers.

Encode data based on the context in which it appears:

ContextRisksEncoding Technique
HTML Body<script>, <img src=x onerror=...>Use HTML entity encoding (e.g., <&lt;, >&gt;).
HTML Attributesvalue=""><script>...</script>Encode quotes and special characters (e.g., "&quot;, '&#39;).
JavaScriptvar x = "userInput";"; maliciousCode(); "Use JavaScript string encoding (escape backslashes, quotes, newlines).
URLshttp://example.com?param=userInputUse URL encoding (URLEncoder.encode() for query parameters).

Tools for Encoding in JSP/Servlets:#

  • JSTL <c:out> Tag: Automatically escapes XML/HTML entities.
    Example: <c:out value="${userInput}" escapeXml="true" /> (default: escapeXml="true").
  • OWASP Java Encoder: A library for context-aware encoding (see Section 4).

3. Leverage JSTL and Expression Language (EL) Safely#

JSPs should use JSTL (JavaServer Pages Standard Tag Library) and EL instead of scriptlets (<% %>), which bypass automatic escaping and are error-prone.

Critical Practices:#

  • Avoid Scriptlets: Never use <%= userInput %>—it directly outputs unescaped data.
  • Use <c:out> for Dynamic Content: Even with EL, <c:out> ensures strict escaping.
    Vulnerable: <p>Welcome, ${user.name}</p> (if user.name contains <script>).
    Secure: <p>Welcome, <c:out value="${user.name}" /></p>.
  • Escape EL in HTML Attributes: EL in attributes (e.g., <input value="${param.name}">) may not auto-escape. Use <c:out> here too:
    <input type="text" value="<c:out value='${param.name}' />">.

4. Use OWASP Libraries: OWASP Java Encoder#

For advanced encoding (e.g., JavaScript, CSS, or non-HTML contexts), use the OWASP Java Encoder library. It provides context-specific encoding utilities.

Setup:#

Add the Maven dependency:

<dependency>
    <groupId>org.owasp.encoder</groupId>
    <artifactId>encoder</artifactId>
    <version>1.2.3</version> <!-- Check for latest version -->
</dependency>

Example Usage:#

import org.owasp.encoder.Encode;
 
// HTML body encoding
String htmlEncoded = Encode.forHtml(userInput);
 
// HTML attribute encoding (e.g., in a Servlet setting a JSP attribute)
String attrEncoded = Encode.forHtmlAttribute(userInput);
request.setAttribute("safeAttribute", attrEncoded);
 
// JavaScript encoding (e.g., in a <script> block)
String jsEncoded = Encode.forJavaScript(userInput);
out.println("var username = '" + jsEncoded + "';");

In JSPs, use EL functions with OWASP Encoder (requires a custom tag library—see OWASP docs).

5. Secure Session Management: Protect Cookies#

XSS attackers often steal session cookies to impersonate users. Mitigate this with:

  • HttpOnly Cookies: Prevent client-side scripts from accessing cookies.
  • Secure Cookies: Ensure cookies are only sent over HTTPS.
  • SameSite Cookies: Restrict cookies to same-site requests (mitigate CSRF, but also XSS).

Example: Setting Secure Cookies in a Servlet

Cookie sessionCookie = new Cookie("JSESSIONID", request.getSession().getId());
sessionCookie.setHttpOnly(true); // Block JS access
sessionCookie.setSecure(true);   // Only over HTTPS
sessionCookie.setSameSite(Cookie.SameSite.STRICT); // or LAX
response.addCookie(sessionCookie);

6. Implement Content Security Policy (CSP)#

CSP is a browser security layer that restricts which scripts, styles, and resources can run on a page. It blocks unauthorized inline scripts (a common XSS vector).

How to Enable CSP:#

Add a Content-Security-Policy HTTP header via a Servlet Filter:

@WebFilter("/*")
public class CSPFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        // Allow scripts only from self and trusted CDNs; block inline scripts/eval
        httpResponse.setHeader("Content-Security-Policy", 
            "default-src 'self'; " +
            "script-src 'self' https://trusted-cdn.com; " +
            "style-src 'self' https://trusted-cdn.com; " +
            "img-src 'self' data:; " +
            "object-src 'none'; " + // Block plugins like Flash
            "frame-ancestors 'none';"); // Prevent clickjacking
        chain.doFilter(request, response);
    }
}

Tip: Use Content-Security-Policy-Report-Only first to test CSP without blocking content.

7. Avoid Dangerous APIs#

Certain Java and JSP APIs are inherently risky for XSS. Avoid these:

  • Scriptlets (<% %>): Directly embed unescaped code.
  • out.println() in Servlets: Outputting user input without encoding:
    Vulnerable: response.getWriter().println("<p>Welcome, " + userInput + "</p>");
    Secure: Use OWASP Encoder first: response.getWriter().println("<p>Welcome, " + Encode.forHtml(userInput) + "</p>");
  • innerHTML in Client-Side JS: Avoid inserting user input into the DOM with innerHTML—use textContent instead, or sanitize with libraries like DOMPurify.

8. Regular Security Audits and Testing#

Even with secure code, new vulnerabilities can emerge. Test rigorously:

  • Automated Scanning: Use tools like OWASP ZAP or Burp Suite to scan for XSS.
  • Manual Testing: Inject payloads like <script>alert('XSS')</script>, <img src=x onerror=alert(1)>, or javascript:alert(1) into inputs and URLs.
  • Static Application Security Testing (SAST): Use tools like SonarQube to detect insecure coding patterns (e.g., unescaped EL, scriptlets).

Example Scenarios: Vulnerable vs. Secure Code#

Scenario 1: Reflected XSS in a Search JSP#

Vulnerable JSP (uses scriptlet to output URL parameter):

<%@ page import="javax.servlet.http.HttpServletRequest" %>
<html>
  <body>
    <h1>Search Results for: <%= request.getParameter("query") %></h1>
    <!-- ... results ... -->
  </body>
</html>

Attack: http://example.com/search.jsp?query=<script>stealCookies()</script>
Fix: Use JSTL <c:out>:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
  <body>
    <h1>Search Results for: <c:out value="${param.query}" /></h1>
    <!-- ... results ... -->
  </body>
</html>

Scenario 2: Stored XSS in a Comment System#

Vulnerable Servlet (stores unvalidated comment):

@WebServlet("/add-comment")
public class CommentServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        String comment = request.getParameter("comment");
        // Store comment in database WITHOUT validation/encoding
        commentDAO.save(comment); 
        response.sendRedirect("comments.jsp");
    }
}

Vulnerable JSP (renders stored comment unescaped):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:forEach var="comment" items="${comments}">
  <div class="comment">${comment.text}</div> <!-- UNSAFE! -->
</c:forEach>

Fix: Validate input in the Servlet and encode output in JSP:

  1. Validate in Servlet:
    String comment = request.getParameter("comment");
    if (!Pattern.matches("^[a-zA-Z0-9 .,?!'-]{1,500}$", comment)) {
        request.setAttribute("error", "Invalid comment.");
        request.getRequestDispatcher("add-comment.jsp").forward(request, response);
        return;
    }
    commentDAO.save(comment); // Now safe to store
  2. Encode in JSP:
    <c:forEach var="comment" items="${comments}">
      <div class="comment"><c:out value="${comment.text}" /></div> <!-- SAFE -->
    </c:forEach>

Conclusion#

XSS is a persistent threat, but it’s preventable with careful coding. By combining input validation, context-aware output encoding, secure JSP practices (JSTL/EL), OWASP libraries, and runtime protections like CSP, you can significantly reduce risk.

Remember: No single technique is foolproof. Adopt a defense-in-depth strategy, stay updated on OWASP guidelines, and test rigorously. Your users’ security depends on it.

References#