Ok finally I did it, I post my solution as response instead of comment, it is functional but no very robust, if you want me to improve it with exception handlers etc let me know
The AntiXssDemoApplication.java is
package com.melardev.stackoverflow.demos.antixssdemo;
import com.melardev.stackoverflow.demos.antixssdemo.filters.AntiXssFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.servlet.Filter;
@SpringBootApplication
@ServletComponentScan
public class AntiXssDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AntiXssDemoApplication.class, args);
}
}
the AntiXssFilter
package com.melardev.stackoverflow.demos.antixssdemo.filters;
import org.springframework.web.util.HtmlUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class AntiXssFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter initialized");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String userInput = servletRequest.getParameter("param");
if (userInput != null && !userInput.equalsIgnoreCase(HtmlUtils.htmlEscape(userInput)))
throw new RuntimeException();
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
The Controller
package com.melardev.stackoverflow.demos.antixssdemo.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/")
public class HomeController {
@RequestMapping("/xss-reflected")
@ResponseBody
public String xssDemo(@RequestParam("param") String userInput) {
return userInput;
}
}
The demo:
- open browser at localhost:8080/xss-reflected?param=Look at this reflected content, works!!
- open browser at localhost:8080/xss-reflected?param=<h1>Look at this reflected content, works!!</h1>
At step 2, I have used the html tag h2. You should see a Runtime exception thrown from Filter, what happened is:
The Filter intercepts all urls(because of urlPatterns=/**), for each interception doFilter is called, if the user supplied Html content, then HtmlUtils.htmlEscape will return the filtered string, in other words, the returned string is different from the original one, this means the user supplied Html in his json input, which is not what we expect, so we throw the exception,
if the returned string is the same as the string returned by htmlEscape(userInput) this means the user has not supplied any Html content, in that case we let the request pipeline to flow as usual with filterChain.doFilter(servletRequest, servletResponse);
I am not using a live XSS demo because chrome will most likely protect you since it is a very basic reflected XSS detected by anyone ...
The Spring Boot skeleton project was downloaded from https://start.spring.io/
with Web as the only starter dependency.
Edit: Improved code