XSS解决方案(以及前端UrlEncode 加密两次的原理分析);附代码

 XSSFilter.java
package com.sfpay.scfp.oms.app.filter;

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;
import javax.servlet.http.HttpServletResponse;

 
/**
 * Servlet Filter implementation class XSSFilter
 */
public class XSSFilter implements Filter {

  
 
	/**
	 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
	 */
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

		if (!(request instanceof HttpServletRequest)) {
			chain.doFilter(request, response);
			return;
		}
		HttpServletRequest hrequest = (HttpServletRequest) request;
		HttpServletResponse hresponse = (HttpServletResponse) response;
		XSSRequestWrapper secureRequest = new XSSRequestWrapper(hrequest);
		chain.doFilter(secureRequest, hresponse);

	
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}

 

}

XSSRequestWrapper.java
package com.sfpay.scfp.oms.app.filter;

import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
 * 
 * @author wzs
 *
 */
public class XSSRequestWrapper extends HttpServletRequestWrapper implements
		HttpServletRequest {
	HttpServletRequest orgRequest = null;

	public XSSRequestWrapper(HttpServletRequest servletRequest) {
		super(servletRequest);
		orgRequest = servletRequest;
	}
   
	 
	public boolean getStatus() {
		  boolean flag=true;
 		  Enumeration<String> parameterNames = super.getParameterNames();
 		 while(parameterNames.hasMoreElements()){
 			 String nextElement = parameterNames.nextElement();
 			 String parameter = this.getParameter(nextElement);
 			  if(parameter.contains("script")){
 				 flag=false;
 				  break;
 			  }
 		  }
 		 return flag;
	}

	@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;
	}

	@Override
	public String getParameter(String parameter) {
		String value = super.getParameter(parameter);
		if (value == null) {
			return null;
		}
		return cleanXSS(value);
	}

	@Override
	public String getHeader(String name) {
		String value = super.getHeader(name);
		if (value == null) {
			return null;
		}
		return cleanXSS(value);
	}

	private String cleanXSS(String value) {
		if (value == null || value.isEmpty()) {
			return value;
		}
		// 后台在参数传递时已经进行了encode,此处需要decode。
		/**
		 * 注意:前段js对url进行两次加密 UrlEncode(UrlEncode(URL)),目的是:前台第一次通过加密使用前后台约定编码。比如:UTF-8
		 *  加密完成以后,此时的url基本上就是%、字母、数字了。第二次加密就是对%、字母、数字进行加密了。此时无论使用哪一种编码集
		 *  加密后的结果都是一样的,(UTF-8/GBK/ISO-8859-1),
		 *   后台,只需要显示的调用一下URLDecoder.decode(value, "UTF-8"); 就可以了。
		 *   因为request.getParameter("name")之前会自动做一次解码的工作,而且是默认的ISO-8859-1。
		 *   然后通过显示的调用URLDecoder.decode(value, "UTF-8");就得到我们想要的url了
		 *   可参考:http://blog.csdn.net/kongqz/article/details/9028111
		 */
		try {
			value = URLDecoder.decode(value, "UTF-8");
		} catch (Exception e) {
			/*
			 *  显示模板保存时未进行encode,出现%时decode会抛异常,异常截取改为Exception且不打印堆栈。
			 * 当然,【%】encode后为【%25】,若值包含%25,则会decode为%。
			 */
//			e.printStackTrace();
		}
		// 方案一:对相关Xss特殊字符进行替换,但是,返回前台时数据还是会有问题。
		// You'll need to remove the spaces from the html entities below
		// 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", "scr1pt");
		
		// 方案二:采用XssProject进行替换,有bug就是。实现原理是将Xss特殊字段截取。需要完整的html标签,无法只针对<script>...</script>进行处理,直接返回空字符串。
//		StringReader reader = new StringReader(value);
//		StringWriter writer = new StringWriter();
//		try {
//			HTMLParser.process(reader, writer, new XSSFilter(), true);
//			value = writer.toString();
//		} catch (NullPointerException e) {
//			return value;
//		} catch (Exception ex) {
//			ex.printStackTrace(System.out);
//		}
		
		// 方案三:采用Spring提供的函数,跟方案一存在同样的bug。
//		value = HtmlUtils.htmlEscape(value);
		
		// 方案四:采用ESAPI进行处理。相关配置非常麻烦。
		// NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to avoid encoded attacks.
        // value = ESAPI.encoder().canonicalize(value);
		
		// 方案五:采用正则表达式,对相关Xss特殊字符进行替换。后台应该没有需要富文本编辑的功能。
        // Avoid null characters
        value = value.replaceAll("", "");

        // Avoid anything between script tags
        Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
        value = scriptPattern.matcher(value).replaceAll("");

        // Avoid anything in a src='...' type of e­xpression
        scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        value = scriptPattern.matcher(value).replaceAll("");

        scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        value = scriptPattern.matcher(value).replaceAll("");

        // Remove any lonesome </script> tag
        scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
        value = scriptPattern.matcher(value).replaceAll("");

        // Remove any lonesome <script ...> tag
        scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        value = scriptPattern.matcher(value).replaceAll("");

        // Avoid eval(...) e­xpressions
        scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        value = scriptPattern.matcher(value).replaceAll("");

        // Avoid e­xpression(...) e­xpressions
        scriptPattern = Pattern.compile("e­xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        value = scriptPattern.matcher(value).replaceAll("");

        // Avoid javascript:... e­xpressions
        scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
        value = scriptPattern.matcher(value).replaceAll("");

        // Avoid vbscript:... e­xpressions
        scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
        value = scriptPattern.matcher(value).replaceAll("");

        // Avoid onload= e­xpressions
        scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
        value = scriptPattern.matcher(value).replaceAll("");
		
        return value;
	}

	/**
	 * 获取最原始的request
	 * 
	 * @return
	 */
	public HttpServletRequest getOrgRequest() {
		return orgRequest;
	}

	/**
	 * 获取最原始的request的静态方法
	 * 
	 * @return
	 */
	public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
		if (req instanceof XSSRequestWrapper) {
			return ((XSSRequestWrapper) req).getOrgRequest();
		}

		return req;
	}

}

前段两次加密原理测试:可参考:http://blog.csdn.net/kongqz/article/details/9028111

String sArgs="与客户合同中约定利率为12%,系统显示利率为36.4%,故需减免复利差额";
		//第一次加密,核心加密
		String encode = URLEncoder.encode(sArgs, "UTF-8");
		System.out.println(encode);
		//第二次加密,只是对%、字母、数字进行二次加密。使用任何字符集加密的结果是一样的
		String encode2 = URLEncoder.encode(encode, "ISO-8859-1");
		System.out.println(encode2);
		System.out.println(".......................................");
		//第一次解密:对第二次加密的结果进行解密
		String decode = URLDecoder.decode(encode2, "UTF-8");
		System.out.println(decode);
		//第二次解密:核心解密
		String decode2 = URLDecoder.decode(decode, "UTF-8");
		System.out.println(decode2);

对%、字母、数字进行二次加密,使用任何字符集加密的结果是一样的。测试如下;

String sArgs="与客户合同中约定利率为12%,系统显示利率为36.4%,故需减免复利差额";
		//第一次加密,核心加密
		String encode = URLEncoder.encode(sArgs, "UTF-8");
		System.out.println(encode);
		 //str 就是 encode 的值
		String str ="%E4%B8%8E%E5%AE%A2%E6%88%B7%E5%90%88%E5%90%8C%E4%B8%AD%E7%BA%A6%E5%AE%9A%E5%88%A9%E7%8E%87%E4%B8%BA12%25%EF%BC%8C%E7%B3%BB%E7%BB%9F%E6%98%BE%E7%A4%BA%E5%88%A9%E7%8E%87%E4%B8%BA36.4%25%EF%BC%8C%E6%95%85%E9%9C%80%E5%87%8F%E5%85%8D%E5%A4%8D%E5%88%A9%E5%B7%AE%E9%A2%9D";
		String decode = URLEncoder.encode(str, "UTF-8");
		System.out.println(decode);
		String decode2 = URLEncoder.encode(str, "GBK");
		System.out.println(decode2);
		String decode3=  URLEncoder.encode(str, "ISO-8859-1");
		System.out.println(decode3);
        //decode decode2 decode3 的结果是一样的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值