XSS漏洞攻击主要分为两种途径:
1、URL参数传递
2、表单提交参数
先说第一种,这种一般都是做为查询条件传递到后端的,来需要保存到数据库中的,处理方法也简单,只要进行转码或过滤就可以了。但是具体方法也有很多,如
1)直接对参数进行编码:StringEscapeUtils.escapeHtml(searchWords);在传递可页面显示的时候都可以进行。
2)在Spring的控制器中注册属性编辑器
下面是一个简单的Spring命令控制器,扩展AbstractCommandController:
public class AbstractFrontCommandController extends AbstractCommandController{
public AbstractFrontCommandController(){//只是为满足AbstractCommandController的要求
this.setCommandClass(String.class);
}
@Override
protected final void initBinder(HttpServletRequest request,ServletRequestDataBinder binder) throws Exception {
super.initBinder(request, binder);
binder.registerCustomEditor(String.class,new StringEscapeEditor(true, false, false));
}
@Override
protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
return null;
}
}
public class StringEscapeEditor extends PropertyEditorSupport {
private boolean escapeHTML;
private boolean escapeJavaScript;
private boolean escapeSQL;
public StringEscapeEditor() {
super();
}
public StringEscapeEditor(boolean escapeHTML, boolean escapeJavaScript, boolean escapeSQL) {
super();
this.escapeHTML = escapeHTML;
this.escapeJavaScript = escapeJavaScript;
this.escapeSQL = escapeSQL;
}
public void setAsText(String text) {
if (text == null) {
setValue(null);
} else {
String value = text;
if (escapeHTML) {
value = StringEscapeUtils.escapeHtml(value);
}
if (escapeJavaScript) {
value = StringEscapeUtils.escapeJavaScript(value);
}
if (escapeSQL) {
value = StringEscapeUtils.escapeSql(value);
}
setValue(value);
}
}
public String getAsText() {
Object value = getValue();
return (value != null ? value.toString() : "");
}
}
这两个类都比较简单,还有其它的控制器都可以这么做,如SimpleFormController等。
3)直接写一个XSS Filter的过滤器,在期间进行编码,这样更简单,但牺牲性能。
public class CrossScriptingFilter implements Filter {
/* (non-Javadoc)
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
CrossScriptingRequestWrapper xssRequest = new CrossScriptingRequestWrapper((HttpServletRequest) request);
filterChain.doFilter(xssRequest, response);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
package net.eprow.b2b.web.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
/**
* <code>{@link CrossScriptingRequestWrapper}</code>
*
* @author wqf
*/
public class CrossScriptingRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
public CrossScriptingRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
/**
* 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/>
* 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
* getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(xssEncode(name));
if (value != null) {
value = xssEncode(value);
}
return value;
}
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;
}
/**
* 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/>
* 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
* getHeaderNames 也可能需要覆盖
*/
@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 value) {
if(StringUtils.isBlank(value)) return value;
return StringEscapeUtils.escapeHtml(value);
//或者下面的方法直接过滤特殊字符,但有时候不能这么做。
// value = value.replace('<',' ');
// value = value.replace('>',' ');
// value = value.replace('"',' ');
// value = value.replace('\'',' ');
// value = value.replace('/',' ');
// value = value.replace('%',' ');
// value = value.replace(';',' ');
// value = value.replace('(',' ');
// value = value.replace(')',' ');
// value = value.replace('&',' ');
// value = value.replace('+','_');
//或者直接转换成全角字符,有时候也不适用。
// StringBuilder sb = new StringBuilder(s.length() + 16);
// for (int i = 0; i < s.length(); i++) {
// char c = s.charAt(i);
// switch (c) {
// case '>':
// sb.append('>');//全角大于号
// break;
// case '<':
// sb.append('<');//全角小于号
// break;
// case '\'':
// sb.append('‘');//全角单引号
// break;
// case '\"':
// sb.append('“');//全角双引号
// break;
// case '&':
// sb.append('&');//全角
// break;
// case '\\':
// sb.append('\');//全角斜线
// break;
// case '#':
// sb.append('#');//全角井号
// break;
// default:
// sb.append(c);
// break;
// }
// }
// return sb.toString();
}
/**
* 获取最原始的request
*
* @return
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取最原始的request的静态方法
*
* @return
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
if (req instanceof CrossScriptingRequestWrapper) {
return ((CrossScriptingRequestWrapper) req).getOrgRequest();
}
return req;
}
}
每二种方式就比较麻烦了,Spring也自带了一些处理方法:
1)web.xml中添加
<context-param>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
2)在包含form的jsp页面中添加
<spring:htmlEscape defaultHtmlEscape="true" />
3)直接在form中的元素中添加
<form:input path="someFormField" htmlEscape="true" />
或
<form:form htmlEscape="true">
4)JSTL输出
<c:out value="${formulario}" escapeXml="true" />默认
escapeXml
就为true
或
${fn:escapeXml(param.nextUrl)}
值得注意的是,不要在后台处理的时候进行编码或过滤,因为后台处理需要真实的数据,真正到页面输出的时候才需要做转义。
阅读全文……