Cross-Site Scripting (XSS) Attack Remediation
Learn more about prevention and remediation after a cross-site scripting attack.
Join the DZone community and get the full member experience.
Join For FreeFor more details on XSS attacks, please refer to OWASP and this OWASP Prevention cheat sheet.
Preventing XSS
Various factors should be considered while acting on XSS Attacks, for example:
- Input type in the HTTP request
- Locations of the HTML document where data would be included
Note
- A defense that works in one context (such as an HTML attribute) might not work in another context (such as a JavaScript variable assignment)
- A defense that works with one kind of input (such as input validation and output encoding for a username) will not work with other kinds of input (such as sanitization for untrusted HTML).
We need to use a different output encoding function based on where you are inserting untrusted data into the webpage!
OWASP Java Encoder Project
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder-jsp</artifactId>
<version>1.2.1</version>
</dependency>
Here are a few examples of different contexts in an HTML page in a JSP:
Encoding
HTML
The following encoding functions are used to safely place untrusted data into different locations of an HTML document.
API | Coments |
---|---|
Encode.forHtml(UNTRUSTED) | If you are unsure, which one to use, you can use this in HTML context |
Encode.forHtmlAttribute(UNTRUSTED) | |
Encode.forHtmlContent(UNTRUSTED) | |
Encode.forHtmlUnquotedAttribute(UNTRUSTED) |
JavaScript
The following encoding functions are used to safely place untrusted data into different locations of JavaScript code.
API | Comments |
---|---|
Encode.forJavaScript(UNTRUSTED) | If you are unsure, which one to use, you can use this in JavaScript context |
Encode.forJavaScriptAttribute(UNTRUSTED) | |
Encode.forJavaScriptBlock(UNTRUSTED) | |
Encode.forJavaScriptSource(UNTRUSTED) |
CSS
The following encoding functions are used to safely place untrusted data into different locations of dynamic CSS code.
API | Comments |
Encode.forCssString(UNTRUSTED) |
<div>Encode.forCssString(UNTRUSTED) %>”> |
Encode.forCssUrl(UNTRUSTED) |
<div>Encode.forCssUrl(UNTRUSTED) %>”> |
XML
API | Comments |
---|---|
Encode.forXML(UNTRUSTED) | |
Encode.forXMLContent(UNTRUSTED) | |
Encode.forXMLAttribute(UNTRUSTED) | |
Encode.forXMLComment(UNTRUSTED) | |
Encode.forCDATA(UNTRUSTED) |
Tag Lib
Prefer tag libs while working with jsps:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@taglib prefix="e" uri="https://www.owasp.org/index.php/OWASP_Java_Encoder_Project" %>
<html>
<head>
<title><e:forHtml value="${param.title}" /></title>
</head>
<body>
<h1>${e:forHtml(param.data)}</h1>
</body> P
For more details, refer to the OWASP Java Encoder Project Docs.
Wrapper Class
import javax.servlet.http.HttpServletRequest;
import org.owasp.encoder.Encode;
public class SafeDataBuilder {
private String paramVal = null;
private SafeDataBuilder(HttpServletRequest request, String paramName) {
String val = getValue(request, paramName);
this.paramVal = cleanValue(val);
}
private String cleanValue(String val) {
String result = val;
if (val != null && val.length() > SecurityConstants.MAX_HTTP_PARAM_LENGTH) {
result = val.substring(0, SecurityConstants.MAX_HTTP_PARAM_LENGTH);
}
return result;
}
private String getValue(HttpServletRequest request, String paramName) {
String val = request.getParameter(paramName);
if (val == null) {
Object attr = request.getAttribute(paramName);
if (attr instanceof String) {
val = (String) attr;
}
if (attr instanceof Boolean) {
val = attr.toString();
}
}
return val;
}
public static SafeDataBuilder requestParam(HttpServletRequest request, String paramName) {
return new SafeDataBuilder(request, paramName);
}
public SafeDataBuilder defaultValue(String val) {
if (this.paramVal == null) {
this.paramVal = val;
}
return this;
}
public SafeDataBuilder trim() {
if (this.paramVal != null) {
this.paramVal = paramVal.trim();
}
return this;
}
public SafeDataBuilder forHtml() {
if (this.paramVal != null) {
this.paramVal = Encode.forHtml(this.paramVal);
}
return this;
}
public SafeDataBuilder forHtmlContent() {
if (this.paramVal != null) {
this.paramVal = Encode.forHtml(this.paramVal);
}
return this;
}
public SafeDataBuilder forHtmlAttribute() {
if (this.paramVal != null) {
this.paramVal = Encode.forHtml(this.paramVal);
}
return this;
}
public SafeDataBuilder forFormActionAttribute() {
if (this.paramVal != null) {
this.paramVal = Encode.forHtmlAttribute(cleanJsAttribute(this.paramVal));
}
return this;
}
private String cleanJsAttribute(String action) {
String result = action;
String cleansed = action.trim().toLowerCase();
if (cleansed.startsWith("javascript")) {
result = cleansed.substring(10);
}
return result;
}
public SafeDataBuilder forJavaScript() {
if (this.paramVal != null) {
this.paramVal = Encode.forJavaScript(this.paramVal);
}
return this;
}
public SafeDataBuilder forJavaScriptAttribute() {
if (this.paramVal != null) {
this.paramVal = Encode.forJavaScriptAttribute(this.paramVal);
}
return this;
}
public SafeDataBuilder forJavaScriptBlock() {
if (this.paramVal != null) {
this.paramVal = Encode.forJavaScriptBlock(this.paramVal);
}
return this;
}
public SafeDataBuilder forJavaScriptSource() {
if (this.paramVal != null) {
this.paramVal = Encode.forJavaScriptSource(this.paramVal);
}
return this;
}
public SafeDataBuilder forCssString() {
if (this.paramVal != null) {
this.paramVal = Encode.forCssString(this.paramVal);
}
return this;
}
public SafeDataBuilder forCssUrl() {
if (this.paramVal != null) {
this.paramVal = Encode.forCssUrl(this.paramVal);
}
return this;
}
public SafeDataBuilder forUriComponent() {
if (this.paramVal != null) {
this.paramVal = Encode.forUriComponent(this.paramVal);
}
return this;
}
public String get() {
return this.paramVal;
}
public String getQuoted() {
return "'" + this.paramVal + "'";
}
}
Usage
String paramVal = SafeDataBuilder.requestParam(request, "paramName").forHtml().get();
String paramVal = SafeDataBuilder.requestParam(request, "paramName").defaultValue("def").forHtml().get();
String paramVal = SafeDataBuilder.requestParam(request, "paramName").defaultValue("def").trim().forHtml().get();
Solution
Refer to the project for more details:
- Define a config for each page.
- The config describes what are all parameters (and XSS type) used by the page
- Configure an XSS filter (
XSSFilter
) for every request, which wraps anhttpservelet
request (XSSRequestWrapper
). - Eventually, every page has
XSSRequestWrapper
asHTTPServletRequest
, whenevergetParameter
happens, config is read to the determine the XSS type, and data is stripped accordingly. - Further, we may have to define one more method in
XSSRequestWrapper
getParameter
(name, type) in case the need arises. - Further, we may have to define one more method in
XSSRequestWrapper
getOriginalParameter
(name) in case if the need arises. - Further by default, for each parameter p, there should other for example
p_xss_s_xss_type
(for eachxss_type
)
References
Published at DZone with permission of Mohamed Sanaulla, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments