Prevent Spring Boot Api from XSS Attacks

Tharun Varshanth Krishnamoorthy
4 min readMar 9, 2024

Cross-site script attacks a code injection attack. The attacker aims to execute malicious scripts in a web browser. This script can be injected by the user through user inputs.

XSS attack Is when the attacker executes some unwanted or else something that makes users do nothing on the site that kind of JavaScript code in a user’s browser.

To run malicious JavaScript code in a victim’s browser, an attacker must first find a way to inject malicious code (payload) into a web page that the victim visits.

Cross-site Scripting may also deface a website instead of targeting the user. The attacker can use injected scripts to change the content of the website or even redirect the browser to another web page, for example, one that contains malicious code.

The end user’s browser cannot know that the script should not be trusted and will execute the script. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by the browser and used with that site. These scripts can even rewrite the content of the HTML page. For more details on the different types of XSS flaws.

Usually, Web APIs that return data in the form of HTML, XML, or JSON can make the chance of client-side, if user inputs are not sanitized properly, depending on how much trust the client app places in the API.

To prevent XSS attacks, web APIs should implement input validation and output encoding. Input validation ensures that user input meets the expected criteria and doesn’t include malicious code. Output encoding ensures that any data returned by the API is properly sanitized so that it can’t be executed as code by the user’s browser.

Let’s not look at our Spring boot application to prevent XSS attacks.

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
try {
RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest, skipWords);

String uri = requestWrapper.getRequestURI();
System.out.println("getRequestURI : " + uri);
String decodedURI = URLDecoder.decode(uri, "UTF-8");
System.out.println("decodedURI : " + decodedURI);

// XSS: Path Variable Validation
if (!XSSValidationUtils.isValidURL(decodedURI, skipWords)) {
ErrorResponse errorResponse = new ErrorResponse();

errorResponse.setStatus(HttpStatus.FORBIDDEN.value());
errorResponse.setMessage("XSS attack error");
System.out.println("convertObjectToJson(errorResponse) : " + convertObjectToJson(errorResponse));
servletResponse.getWriter().write(convertObjectToJson(errorResponse));
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
return;
}

System.out.println("Response output: " + requestWrapper.getBody());
if (!StringUtils.isEmpty(requestWrapper.getBody())) {

// XSS: Post Body data validation
if (XSSValidationUtils.isValidURLPattern(requestWrapper.getBody(), skipWords)) {

filterChain.doFilter(requestWrapper, servletResponse);
} else {
ErrorResponse errorResponse = new ErrorResponse();

errorResponse.setStatus(HttpStatus.FORBIDDEN.value());
errorResponse.setMessage("XSS attack error");
servletResponse.getWriter().write(convertObjectToJson(errorResponse));
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
return;

}
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
} catch (RuntimeException ex) {
servletResponse.getWriter().write(ex.getMessage());
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
} catch (Exception ex) {
servletResponse.getWriter().write(ex.getMessage());
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
} finally {
System.out.println("clean up");
}
}

The Filter class was imported from jakarta.servlet package, it was acting as middleware in the spring boot application, before the API Request reaches controller classes it will validate our API request. So, all the API requests should come and pass through the doFilter method. So here we will write input validations and can prevent the Database.

Inside the doFilter method, we going to validate.

· Weather URL has valid parameters.

· Do the validation against with request body.

@UtilityClass
public class XSSValidationUtils {

public final Pattern pattern = Pattern.compile("^[a-zA-Z0-9!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.\\/?\\s]*$", Pattern.CASE_INSENSITIVE);


public static boolean isValidURL(String uri, List<String> skipWords) {
AtomicBoolean flag= new AtomicBoolean(false);
String[] urls=uri.split("\\/");

Arrays.stream(urls).filter(e->!StringUtils.isEmpty(e)).forEach(url->{
String val=String.valueOf(url);

if(skipWords.stream().anyMatch(p->val.toLowerCase().contains(p.toLowerCase()))){
System.out.println("bad char found!!!!!");
flag.set(true);
}
Matcher matcher = pattern.matcher(val);
if (!matcher.matches()) {
System.out.println("Invalid char found!!!!!");
flag.set(true);
}else{
System.out.println("valid char found: "+val);
}
});
return !flag.get();
}

public static boolean isValidRequestParam(String param, List<String> skipWords) {
AtomicBoolean flag= new AtomicBoolean(false);
String[] paramList=param.split("&");

Arrays.stream(paramList).filter(e->!StringUtils.isEmpty(e)).forEach(url->{
String val=String.valueOf(url);

if(skipWords.stream().anyMatch(val::equalsIgnoreCase)){
System.out.println("bad char found!!!!!");
flag.set(true);
}
Matcher matcher = pattern.matcher(val);
if (!matcher.matches()) {
System.out.println("Invalid char found!!!!!");
flag.set(true);
}else{
System.out.println("valid char found: "+val);
}
});
return !flag.get();
}


public static boolean isValidURLPattern(String uri, List<String> skipWords) {
AtomicBoolean flag= new AtomicBoolean(false);
String[] urls=uri.split("\\/");

try {
Arrays.stream(urls).filter(e -> !StringUtils.isEmpty(e)).forEach(url -> {
String val = String.valueOf(url);
Map<String, Object> mapping = jsonToMap(new JSONObject(val));
mapping.forEach((key, value) -> {

if (skipWords.stream().anyMatch(String.valueOf(value)::equalsIgnoreCase)) {
System.out.println("bad char found!!!!!");
flag.set(true);
}
Matcher matcher = pattern.matcher(String.valueOf(value));
if (!matcher.matches()) {
System.out.println(key + " : Invalid char found!!!!!");
flag.set(true);
} else {
System.out.println("valid char found: " + String.valueOf(value));
}
});

});
}catch(Exception ex){
flag.set(true);
}
return !flag.get();
}

}

The above class will find the unwanted script or words available and send the relevant error to the end user.

Conclusion

Above are some of the most common pathways through which Cross-site Scripting (XSS) attacks can infiltrate web applications. While we’ve addressed many aspects, it’s essential to remain vigilant as there are numerous avenues attackers may exploit. I trust this information aids in fortifying your application’s security.

These measures are pivotal in bolstering a web application’s defenses against XSS attacks!

https://github.com/Tharunvarshanth/spring-boot-xss-attacks-prevention

--

--