Spring MVC Xss Filter
XSS (Cross Site Scripting): 跨站腳本攻擊, 是Web程序中最常見的漏洞。指攻擊者在網頁中嵌入客戶端腳本(例如JavaScript), 當用戶瀏覽此網頁時,腳本就會在用戶的瀏覽器上執行,從而達到攻擊者的目的.
比如獲取用戶的Cookie,導航到惡意網站,攜帶木馬等。
一般現代應用MVC實現spring mvc的實現比較廣泛,因此,本文章描述spring mvc 的實現xss filter。
第一步
在web.xml中配置filter:
<!-- Xss-html-filter -->
<filter>
<filter-name>xssHttpServletFilter</filter-name>
<filter-class>com.x.y.filter.XssHttpServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssHttpServletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第二步
創建過濾器
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* 包裝原生servlet對象,處理xss問題
*
*/
public class XssHttpServletFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(XssHttpServletFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
} catch (Exception e) {
LOGGER.error("Xss過濾器,包裝request對象失敗");
chain.doFilter(request, response);
}
}
}
第三步
創建request包裝對象
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* 請求包裝對象 處理Xss
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
/**
* 覆蓋getParameter方法,將參數名和參數值都做xss過濾
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
/**
* 覆蓋getParameterValues方法,將參數名和參數值都做xss過濾
*/
public String[] getParameterValues(String 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] = xssEncode(values[i]);
}
return encodedValues;
}
/**
* 獲取request的屬性時,做xss過濾
*/
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (null != value && value instanceof String) {
value = xssEncode((String) value);
}
return value;
};
/**
* 覆蓋getHeader方法,將參數名和參數值都做xss過濾。<br/>
*/
@Override
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
/**
* 將容易引起xss漏洞的半角字符直接替換成全角字符
*
* @param s
* @return
*/
private static String xssEncode(String s) {
if (s == null || s.isEmpty()) {
return s;
}
try {
HTMLFilter htmlFilter = new HTMLFilter();
String clean = htmlFilter.filter(s);
return clean;
} catch (NullPointerException e) {
return s;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
/**
* 獲取最原始的request
*
* @return
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 獲取最原始的request的靜態方法
*
* @return
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) req).getOrgRequest();
}
return req;
}
}
第四步
引用 html filter 的實現。採用htmlfitler 開源實現:xss-html-filter
關鍵點
Spring mvc 解析參數時,使用方法:webRequest.getParameterValues(name)獲取參數,因此,request封裝對象只有實現了getParameterValues(String
parameter)方法,纔會過濾到controller method 的入參。
Open Declaration Object org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
.resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
Object arg;
if (MultipartFile.class.equals(parameter.getParameterType())) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFile(name);
}
else if (isMultipartFileCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFiles(name);
}
else if (isMultipartFileArray(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
arg = multipartFiles.toArray(new MultipartFile[multipartFiles.size()]);
}
else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
assertIsMultipartRequest(servletRequest);
arg = servletRequest.getPart(name);
}
else if (isPartCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = new ArrayList<Object>(servletRequest.getParts());
}
else if (isPartArray(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = RequestPartResolver.resolvePart(servletRequest);
}
else {
arg = null;
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
String[] paramValues = webRequest.getParameterValues(name);
if (paramValues != null) {
arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}
}
}
return arg;
}