XSS 解决方案
前言
我们先看一篇比较常见的解决方式:
实现
拦截器配置
- web.xml
<filter>
  <filter-name>XssSqlFilter</filter-name>
<!-- 文件路径 -->
  <filter-class>com.xxx.XssFilter</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>XssSqlFilter</filter-name>
<!-- 拦截请求路径 -->
  <url-pattern>/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
 </filter-mapping>
拦截器实现
- XssFilter
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class XssFilter implements Filter {
 FilterConfig filterConfig = null;
 public void init(FilterConfig filterConfig) throws ServletException {
  this.filterConfig = filterConfig;
 }
 public void destroy() {
  this.filterConfig = null;
 }
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
   throws IOException, ServletException {
  //调用重写后的请求
  chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
 }
}
- XssHttpServletRequestWrapper.java
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper{
 public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
  super(servletRequest);
 }
 
 @Override
 public Map<String, String[]> getParameterMap(){
  
  Set<String> names = super.getParameterMap().keySet();
  Map<String, String[]> parameterMap = new HashMap<String, String[]>(names.size());
  for (String name : names) {
   String [] values = getParameterValues(name);
   String [] newValues = new String [values.length];
   for(int i=0;i<values.length;i++) {
    newValues[i] = cleanXSS(values[i]);
   }
   parameterMap.put(name, newValues);
   values = null;
   newValues = null;
  }
  return parameterMap;
 }
 /**
  * 重写getParameterValues方法
  */
 @Override
 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] = cleanXSS(values[i]);
  }
  return encodedValues;
 }
 /**
  * 重写getParameter
  */
 @Override
 public String getParameter(String parameter) {
  String value = super.getParameter(parameter);
  //如果为空返回空
  if (value == null) {
   return null;
  }
  //不为空返回过滤后的
  return cleanXSS(value);
 }
 /**
  * 重写getHeader
  */
 @Override
 public String getHeader(String name) {
  //如果为空返回空
  String value = super.getHeader(name);
  if (value == null)
   return null;
  //不为空返回过滤后的
  return cleanXSS(value);
 }
 /**
  * 过滤
  * @param value
  * @return
  */
 private String cleanXSS(String value) {
  String inj_str = "\" ) \' \\* % < > &";
  String inj_stra[] = inj_str.split(" ");
  
  for (int i = 0; i < inj_stra.length; i++) {
   value = value.replaceAll("[" + inj_stra[i] + "]", "");
  }
  String badStr = "and exec execute insert select delete update count drop chr mid master truncate " +
    "char declare sitename net user xp_cmdshell or like and exec execute insert create drop " +
    "table from grant use group_concat column_name " +
    "information_schema.columns table_schema union where select delete update order by count " +
    "chr mid master truncate char declare or like";//过滤掉的sql关键字,可以手动添加
  String badStrs[] = badStr.split(" ");
  for (int i = 0; i < badStrs.length; i++) {
   value = value.replaceAll(badStrs[i].toLowerCase(), "");
   value = value.replaceAll(badStrs[i].toUpperCase(), "");
  }
  value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
  value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
  value = value.replaceAll("'", "& #39;");
  value = value.replaceAll("eval\\((.*)\\)", "");
  value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
  value = value.replaceAll("script", "");
  value = value.replaceAll("iframe", "");
  value = value.replaceAll("img", "");
  return value;
 }
}
ps: 这里也包含了 SQL 注入的过滤。
绕过的方式
大小写
这个绕过方式的出现是因为网站仅仅只过滤了 <script> 标签,而没有考虑标签中的大小写并不影响浏览器的解释所致。
具体的方式就像这样:
http://192.168.1.102/xss/example2.php?name=<sCript>alert("hey!")</scRipt>
利用过滤后返回语句再次构成攻击语句来绕过
这个字面上不是很好理解,用实例来说。
如下图,在这个例子中我们直接敲入script标签发现返回的网页代码中script标签被去除了,但其余的内容并没有改变。
http://192.168.1.102/xss/example3.php?name=<sCri<script>pt>alert("hey!")</scRi</script>pt>
ps: 这其实是一个非常巧妙的方式,当然我们后续会统一给出解决方案。
并不是只有script标签才可以插入代码
在这个例子中,我们尝试了前面两种方法都没能成功,原因在于script标签已经被完全过滤,但不要方,能植入脚本代码的不止script标签。
例如这里我们用 <img> 标签做一个示范。
http://192.168.1.102/xss/example4.php?name=<img
src='w.123' onerror='alert("hey!")'>
就可以再次愉快的弹窗。
原因很简单,我们指定的图片地址根本不存在也就是一定会发生错误,这时候onerror里面的代码自然就得到了执行。
以下列举几个常用的可插入代码的标签。
<a onmousemove=’do something here’> 
当用户鼠标移动时即可运行代码
<div onmouseover=‘do something here’> 
编码脚本代码绕过关键字过滤
有的时候,服务器往往会对代码中的关键字(如alert)进行过滤,这个时候我们可以尝试将关键字进行编码后再插入,不过直接显示编码是不能被浏览器执行的,我们可以用另一个语句eval()来实现。
eval()会将编码过的语句解码后再执行,简直太贴心了。
例如alert(1)编码过后就是
\u0061\u006c\u0065\u0072\u0074(1)
攻击语句:
http://192.168.1.102/xss/example5.php?name=<script>eval(\u0061\u006c\u0065\u0072\u0074(1))</script>
解决方案
思考
如果去穷举各种各样的 Onxxx 函数肯定是不现实的。
我们只需要处理最核心的 < > 之类的特殊字符即可。
这样实现就可以变得非常简单。
实现
- 配置拦截器
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class XssFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        filterChain.doFilter(new XssFilterWrapper((HttpServletRequest) servletRequest), servletResponse);
    }
    @Override
    public void destroy() {
    }
}
XssFilterWrapper 实现如下:
import org.apache.commons.lang.StringUtils;
import org.springframework.web.util.HtmlUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class XssFilterWrapper extends HttpServletRequestWrapper {
    public XssFilterWrapper(HttpServletRequest request) {
        super(request);
    }
    /**
     * 对数组参数进行特殊字符过滤
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        String[] newValues = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            newValues[i] = HtmlUtils.htmlEscape(values[i]);
        }
        return newValues;
    }
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (StringUtils.isNotBlank(value)) {
            return HtmlUtils.htmlEscape(value);
        }
        return value;
    }
}
非常的简单粗暴,就是一个过滤。
拓展阅读
参考资料
https://www.pianshen.com/article/8324902448/
