How do I prevent people from doing XSS in Spring MVC?

SpringJspSpring MvcXssHtml Escape-Characters

Spring Problem Overview


What should I do to prevent XSS in Spring MVC? Right now I am just putting all places where I output user text into JSTL <c:out> tags or fn:escapeXml() functions, but this seems error prone as I might miss a place.

Is there an easy systematic way to prevent this? Maybe like a filter or something? I'm collecting input by specifying @RequestParam parameters on my controller methods.

Spring Solutions


Solution 1 - Spring

In Spring you can escape the html from JSP pages generated by <form> tags. This closes off a lot avenues for XSS attacks, and can be done automatically in three ways:

For the entire application in the web.xml file:

<context-param>
    <param-name>defaultHtmlEscape</param-name>
    <param-value>true</param-value>
</context-param>

For all forms on a given page in the file itself:

<spring:htmlEscape defaultHtmlEscape="true" /> 

For each form:

<form:input path="someFormField" htmlEscape="true" /> 

Solution 2 - Spring

I use Hibernate Validator via @Valid for all input objects (binding and @RequestBody json, see https://dzone.com/articles/spring-31-valid-requestbody). So @org.hibernate.validator.constraints.SafeHtml is a good solution for me.

Hibernate SafeHtmlValidator depends on org.jsoup, so it's needed to add one more project dependencies:

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.1</version>
</dependency>

For bean User with field

@NotEmpty
@SafeHtml
protected String name;

for update attempt with value <script>alert(123)</script> in controller

@PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
public void update(@Valid @RequestBody User user, @PathVariable("id") int id) 

or

@PostMapping
public void createOrUpdate(@Valid User user) {

is thrown BindException for binding and MethodArgumentNotValidException for @RequestBody with default message:

name may have unsafe html content

Validator works as well for binding, as before persisting. Apps could be tested at http://topjava.herokuapp.com/

UPDATE: see also comment from @GuyT

CVE-2019-10219 and status of @SafeHtml

> We have been made aware of a CVE-2019-10219 related to the @SafeHtml constraint and it was fixed in both 6.0.18.Final and 6.1.0.Final.... > > However, we came to the conclusion that the @SafeHtml constraint was fragile, highly security-sensitive and depending on an external library that wasn’t designed for this purpose. Having it included in core Hibernate Validator was not a very good idea. That’s why we deprecated it and marked it for removal.There is no magic plan here so our users will have to maintain this constraint themselves

Resume for myself: it is safe and could be used, until solution better be found.

UPDATE: due to remove @SafeHtml/SafeHtmlValidator from hibernate.validator use own NoHtmlValidator, see https://stackoverflow.com/a/68888601/548473

Solution 3 - Spring

Try XSSFilter.

Solution 4 - Spring

When you are trying to prevent XSS, it's important to think of the context. As an example how and what to escape is very different if you are ouputting data inside a variable in a javascript snippet as opposed to outputting data in an HTML tag or an HTML attribute.

I have an example of this here: http://erlend.oftedal.no/blog/?blogid=91

Also checkout the OWASP XSS Prevention Cheat Sheet: http://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet

So the short answer is, make sure you escape output like suggested by Tendayi Mawushe, but take special care when you are outputting data in HTML attributes or javascript.

Solution 5 - Spring

How are you collecting user input in the first place? This question / answer may assist if you're using a FormController:

Spring: escaping input when binding to command

Solution 6 - Spring

Always check manually the methods, tags you use, and make sure that they always escape (once) in the end. Frameworks have many bugs and differences in this aspect.

An overview: http://www.gablog.eu/online/node/91

Solution 7 - Spring

Instead of relying only on <c:out />, an antixss library should also be used, which will not only encode but also sanitize malicious script in input. One of the best library available is OWASP Antisamy, it's highly flexible and can be configured(using xml policy files) as per requirement.

For e.g. if an application supports only text input then most generic policy file provided by OWASP can be used which sanitizes and removes most of the html tags. Similarly if application support html editors(such as tinymce) which need all kind of html tags, a more flexible policy can be use such as ebay policy file

Solution 8 - Spring

**To avoid XSS security threat in spring application**

> solution to the XSS issue is to filter all the textfields in the form > at the time of submitting the form.

    It needs XML entry in the web.xml file & two simple classes.
    
        java code :-
        The code for the  first class named CrossScriptingFilter.java is :
   
        package com.filter;
        
        import java.io.IOException;
        import javax.servlet.Filter;
        import javax.servlet.FilterChain;
        import javax.servlet.FilterConfig;
        import javax.servlet.ServletException;
        import javax.servlet.ServletRequest;
        import javax.servlet.ServletResponse;
        import javax.servlet.http.HttpServletRequest;
        import org.apache.log4j.Logger;
        
        public class CrossScriptingFilter implements Filter {
        	private static Logger logger = Logger.getLogger(CrossScriptingFilter.class);
            private FilterConfig filterConfig;
        
        	public void init(FilterConfig filterConfig) throws ServletException {
                this.filterConfig = filterConfig;
            }
        
            public void destroy() {
                this.filterConfig = null;
            }
        
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            	logger.info("Inlter CrossScriptingFilter  ...............");
                chain.doFilter(new RequestWrapper((HttpServletRequest) request), response);
                logger.info("Outlter CrossScriptingFilter ...............");
            }
        
        }
        

The code second class named RequestWrapper.java is :

package com.filter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.log4j.Logger;

public final class RequestWrapper extends HttpServletRequestWrapper {
	private static Logger logger = Logger.getLogger(RequestWrapper.class);
	public RequestWrapper(HttpServletRequest servletRequest) {
		super(servletRequest);
	}

	public String[] getParameterValues(String parameter) {
		logger.info("InarameterValues .. parameter .......");
		String[] values = super.getParameterValues(parameter);
		if (values == null) {
			return null;
		}
		int count = values.length;
		String[] encodedValues = new String[count];
		for (int i = 0; i < count; i++) {
			encodedValues[i] = cleanXSS(values[i]);
		}
		return encodedValues;
	}

	public String getParameter(String parameter) {
		logger.info("Inarameter .. parameter .......");
		String value = super.getParameter(parameter);
		if (value == null) {
			return null;
		}
		logger.info("Inarameter RequestWrapper ........ value .......");
		return cleanXSS(value);
	}

	public String getHeader(String name) {
		logger.info("Ineader .. parameter .......");
		String value = super.getHeader(name);
		if (value == null)
			return null;
		logger.info("Ineader RequestWrapper ........... value ....");
		return cleanXSS(value);
	}

	private String cleanXSS(String value) {
		// You'll need to remove the spaces from the html entities below
		logger.info("InnXSS RequestWrapper ..............." + value);
		//value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
		//value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
		//value = value.replaceAll("'", "& #39;");
		value = value.replaceAll("eval\\((.*)\\)", "");
		value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");

		value = value.replaceAll("(?i)<script.*?>.*?<script.*?>", "");
		value = value.replaceAll("(?i)<script.*?>.*?</script.*?>", "");
		value = value.replaceAll("(?i)<.*?javascript:.*?>.*?</.*?>", "");
		value = value.replaceAll("(?i)<.*?\\s+on.*?>.*?</.*?>", "");
		//value = value.replaceAll("<script>", "");
		//value = value.replaceAll("</script>", "");
		logger.info("OutnXSS RequestWrapper ........ value ......." + value);
		return value;
	}

> The only thing remained is the XML entry in the web.xml file:

        <filter>
		<filter-name>XSS</filter-name>
		<display-name>XSS</display-name>
		<description></description>
		<filter-class>com.filter.CrossScriptingFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>XSS</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

The /* indicates that for every request made from browser, it will call CrossScriptingFilter class. Which will parse all the components/elements came from the request & will replace all the javascript tags put by hacker with empty string i.e

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDougView Question on Stackoverflow
Solution 1 - SpringTendayi MawusheView Answer on Stackoverflow
Solution 2 - SpringGrigory KislinView Answer on Stackoverflow
Solution 3 - SpringBozhoView Answer on Stackoverflow
Solution 4 - SpringErlendView Answer on Stackoverflow
Solution 5 - SpringBenView Answer on Stackoverflow
Solution 6 - SpringsibidibaView Answer on Stackoverflow
Solution 7 - SpringrjhaView Answer on Stackoverflow
Solution 8 - SpringGAURAV KUMAR GUPTAView Answer on Stackoverflow