Wlitttle 2016-03-01
公司做的一个web项目中有这个功能,记录并学习下。在web系统中很多页面有文本输入的功能,有些不严谨的程序,通过写一些特殊字符,js片段,sql脚步会导致程序出现bug,现在通过一个统一的功能进行屏蔽。主要通过过滤器、xml解析的机制实现。
1、首先在web.xml里面增加过滤器配置。
<!-- 防sql注入和跨脚本攻击 --> <filter> <filter-name>webSecurityFilter</filter-name> <filter-class>com.zhgl.filter.WebSecurityFilter</filter-class> <init-param> <param-name>filter_xss</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>filter_sql_injection</param-name> <param-value>true</param-value> </init-param> </filter>
2、过滤器功能代码
/** * project : yyzhglpt_public * package :com.zhgl.filter * file : WebSecurityFilter.java */ package com.zhgl.filter; import java.io.IOException; import java.util.Map; import java.util.Set; 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; import com.yyzhglpt.util.StringUtil; import com.zhgl.util.MessageStreamResult; /** * Specification : 文档说明 */ public class WebSecurityFilter implements Filter { public static final String FILTER_XSS_PARAM_NAME = "filter_xss"; public static final String FILTER_SQL_INJECTION_PARAM_NAME = "filter_sql_injection"; boolean filterXSS = true; boolean filterSQL = true; @Override public void destroy() { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; WebXssEscapeFilterUtil filterUtil = WebXssEscapeFilterUtil.getInstance(); String cutUrl = ((HttpServletRequest)request).getRequestURI(); String cutedUrl = cutUrl.substring(cutUrl.lastIndexOf("/") + 1); String actionName = request.getParameter("action"); if (StringUtil.isNotEmpty(cutedUrl) && StringUtil.isNotEmpty(actionName)) { WebXssEscapeFilterRule urlRule = filterUtil.getFilterRule(cutedUrl, actionName); if (urlRule != null) { Map<String, String[]> paramMap = request.getParameterMap(); Set<String> entries = paramMap.keySet(); for (String entry : entries) { String paramName = entry; String[] valueObj = paramMap.get(paramName); if (urlRule.getParams().contains(paramName)) { for(String str: valueObj){ if(!urlRule.getDefender().doFilter(str)){ if("html".equals(urlRule.getResultType())){ try { MessageStreamResult.msgStreamResult(response, "您输入的参数有非法字符,请输入正确的参数!"); return; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }else if("json".equals(urlRule.getResultType())){ try { MessageStreamResult.msgStreamResult(response, "{\"msg\":\"您输入的参数有非法字符,请输入正确的参数!\",\"success\":false}"); return; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } // {\"msg\":\"\",\"success\":true} } } } } } } //WebSecurityWrapper wrapper = new WebSecurityWrapper(servletRequest, WebXssEscapeFilterUtil.getInstance()); filterChain.doFilter(servletRequest, servletResponse); } @Override public void init(FilterConfig config) throws ServletException { String filterXSSParam = config.getInitParameter(FILTER_XSS_PARAM_NAME); String filterSQLParam = config.getInitParameter(FILTER_SQL_INJECTION_PARAM_NAME); filterXSS = new Boolean(filterXSSParam); filterSQL = new Boolean(filterSQLParam); } }
3、哪些action需要进行过滤,具体的哪些字段需要过滤,通过xss-servlet-filter-rule.xml文件配置规则。
<?xml version="1.0" encoding="UTF-8"?> <config> <!-- 默认过滤规则 --> <default> <defender> <name>xssDefaultDefender</name> <class>com.zhgl.filter.defender.DefaultDefender</class> </defender> </default> <!-- url规则设置 --> <url-rule-set> <url-rule><!-- 一个xxx.do一个url-rule --> <url>jxfa.do</url><!-- 拦截的URL --> <actions> <action name="saveFa" resultType="html"><!-- 具体的action名称可多个 请求链接的返回类型 html 为 普通的字符流 json为返回普通接送字符流--> <params> <param name="famc"/><!-- 需要拦截验证是否有特殊字段的字段 目前特殊字符为 < > ' " / & \ --> <param name="fams"/><!-- 需要拦截验证是否有特殊字段的字段 目前特殊字符为 < > ' " / & \ --> </params> </action> <action name="test" resultType="json"> <params> <param name="query1" /> <param name="query2" /> <param name="query3" /> </params> </action> </actions> </url-rule> </url-rule-set> </config>
4、解析xss-servlet-filter-rule.xml文件相关的类。
1)、WebSecurityWrapper类
/** * Copyright 2014 winning, Inc. All rights reserved. * project : yyzhglpt_public * package :com.zhgl.filter * file : WebSecurityWrapper.java * date :2015-11-23 */ package com.zhgl.filter; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * Specification : 文档说明 */ public class WebSecurityWrapper extends HttpServletRequestWrapper { private static final String EVENTS = "((?i)onload|onunload|onchange|onsubmit|onreset" + "|onselect|onblur|onfocus|onkeydown|onkeypress|onkeyup" + "|onclick|ondblclick|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup)"; //private static final String XSS_HTML_TAG = "(%3C)|(%3E)|[<>]+|[']+|[\"]+|(%27)|(%22)|(%2527)"; //private static final String XSS_INJECTION = "((%22%20)|(%22\\s)|('%22)|(%22\\+))\\w.*|(\\s|%20)" + EVENTS + ".*|(%3D)"; //private static final String XSS_REGEX = XSS_HTML_TAG + "|" + XSS_INJECTION; private static final String XSS_REGEX = "(%3C)|(%3E)|[<>]+"; private static final String SQL_REGEX = ""; boolean filterXSS = true; boolean filterSQL = true; public WebSecurityWrapper(HttpServletRequest request, boolean filterXSS, boolean filterSQL) { super(request); this.filterXSS = filterXSS; this.filterSQL = filterSQL; } public WebSecurityWrapper(HttpServletRequest request) { this(request,true,true); } @Override public String getParameter(String name) { String value = super.getParameter(name); return filterParamString(value); } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> rawMap = super.getParameterMap(); Map<String, String[]> filteredMap = new HashMap<String, String[]>(rawMap.size()); Set<String> keys = rawMap.keySet(); for (String key : keys) { String[] rawValue = rawMap.get(key); String[] filteredValue = filterStringArray(rawValue); filteredMap.put(key, filteredValue); } return filteredMap; } protected String[] filterStringArray(String[] rawValue) { String[] filteredArray = new String[rawValue.length]; for (int i = 0; i < rawValue.length; i++) { filteredArray[i] = filterParamString(rawValue[i]); } return filteredArray; } @Override public String[] getParameterValues(String name) { String[] rawValues = super.getParameterValues(name); if (rawValues == null) return null; String[] filteredValues = new String[rawValues.length]; for (int i = 0; i < rawValues.length; i++) { filteredValues[i] = filterParamString(rawValues[i]); } return filteredValues; } protected String filterParamString(String rawValue) { if (rawValue == null) { return null; } if (filterXSS()) { rawValue = rawValue.replaceAll(XSS_REGEX, ""); } if (filterSQL()) { rawValue = rawValue.replaceAll(SQL_REGEX, ""); } return rawValue; } @Override public Cookie[] getCookies() { Cookie[] existingCookies = super.getCookies(); if(existingCookies != null) { for(int i=0;i<existingCookies.length;++i) { Cookie cookie = existingCookies[i]; cookie.setValue(filterParamString(cookie.getValue())); } } return existingCookies; } @Override public String getQueryString() { return filterParamString(super.getQueryString()); } protected boolean filterXSS() { return filterXSS; } protected boolean filterSQL() { return filterSQL; } }
2)、WebXssEscapeFilterConfig类
/* * 文 件 名: WebXssEscapeFilterConfig.java */ package com.zhgl.filter; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import com.zhgl.filter.defender.Defender; /** * <一句话功能简述> <功能详细描述> * * @author 姓名 工号 * @version [版本号, 2015-12-2] * @see [相关类/方法] * @since [产品/模块版本] */ public class WebXssEscapeFilterConfig { private static final String DEFAULT_FILTER_RULE_FILENAME = "xss-servlet-filter-rule.xml"; private Map<String, Map<String, WebXssEscapeFilterRule>> urlRuleSetMap = new HashMap(); private Defender defaultDefender = null; public WebXssEscapeFilterConfig() throws IllegalStateException { this(DEFAULT_FILTER_RULE_FILENAME); } public WebXssEscapeFilterConfig(String filename) throws IllegalStateException { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); Element rootElement = builder.parse(is).getDocumentElement(); addDefaultInfo(rootElement); addUrlRuleSet(rootElement); System.out.println("配置文件初始化"); } catch (Exception e) { throw new IllegalStateException("配置文件xss-servlet-filter-rule.xml读取异常.", e); } } private void addDefaultInfo(Element rootElement) { NodeList nodeList = rootElement.getElementsByTagName("default"); if (nodeList.getLength() > 0) { Element defaultE = (Element)nodeList.item(0); nodeList = defaultE.getElementsByTagName("defender"); if (nodeList.getLength() > 0) { String clazz = getTagContent((Element)nodeList.item(0), "class"); try { defaultDefender = (Defender)Class.forName(clazz.trim()).newInstance(); } catch (Exception e) { throw new IllegalStateException("defender初始失败!", e); } } } } private void addUrlRuleSet(Element rootElement) { NodeList nodeList = rootElement.getElementsByTagName("url-rule"); for (int i = 0; (nodeList.getLength() > 0) && (i < nodeList.getLength()); i++) { Element element = (Element)nodeList.item(i); addUrlRule(element); } } private void addUrlRule(Element element) { Map paramRuleMap = null; String url = null; Element actionsElement = null; NodeList nodeList = element.getElementsByTagName("url"); if (nodeList.getLength() > 0) { url = nodeList.item(0).getTextContent(); } if (url != null) { nodeList = element.getElementsByTagName("actions"); if (nodeList.getLength() > 0) { actionsElement = (Element)nodeList.item(0); if (actionsElement != null) { Map<String,WebXssEscapeFilterRule> ls = addActionParamsRule(actionsElement); if (ls.size() > 0) { urlRuleSetMap.put(url, ls); } } } } } private Map<String, WebXssEscapeFilterRule> addActionParamsRule(Element element) { Map<String, WebXssEscapeFilterRule> ls = new HashMap<String, WebXssEscapeFilterRule>(); NodeList nodeList = element.getElementsByTagName("action"); for (int i = 0; (nodeList.getLength() > 0) && (i < nodeList.getLength()); i++) { Element element_t = (Element)nodeList.item(i); String actionName = element_t.getAttribute("name"); String resultType = element_t.getAttribute("resultType"); if (actionName == null || resultType == null) { continue; } List<String> params = new ArrayList<String>(); NodeList nodeListt = element_t.getElementsByTagName("params"); if (nodeListt.getLength() > 0) { Element element_params = (Element)nodeList.item(0); NodeList nodeListtt = element_params.getElementsByTagName("param"); for (int k = 0; (nodeListtt.getLength() > 0) && (k < nodeListtt.getLength()); k++) { Element element_tt = (Element)nodeListtt.item(k); String paramName = element_tt.getAttribute("name"); if (paramName == null) { continue; } params.add(paramName); } } if (params.size() == 0) { continue; } WebXssEscapeFilterRule rule = new WebXssEscapeFilterRule(); rule.setActionName(actionName); rule.setResultType(resultType); rule.setParams(params); rule.setDefender(defaultDefender); ls.put(actionName, rule); } return ls; } private String getTagContent(Element eachElement, String tagName) { NodeList nodeList = eachElement.getElementsByTagName(tagName); if (nodeList.getLength() > 0) { return nodeList.item(0).getTextContent(); } return ""; } public Defender getDefaultDefender() { return this.defaultDefender; } public WebXssEscapeFilterRule getUrlParamRule(String url, String actionName, String paramName) { Map<String,WebXssEscapeFilterRule> listRule = (Map<String,WebXssEscapeFilterRule>)urlRuleSetMap.get(url); if(listRule == null){ return null; } if( listRule.get(actionName) == null){ return null; }else{ WebXssEscapeFilterRule rule = listRule.get(actionName) ; if( !rule.getParams().contains(paramName) ){ return null; }else{ return rule; } } } public WebXssEscapeFilterRule getUrlParamRule(String url, String actionName) { Map<String,WebXssEscapeFilterRule> listRule = (Map<String,WebXssEscapeFilterRule>)urlRuleSetMap.get(url); if(listRule == null){ return null; } if( listRule.get(actionName) == null){ return null; }else{ WebXssEscapeFilterRule rule = listRule.get(actionName) ; return rule; } } }
3、WebXssEscapeFilterRule类
/* * 文 件 名: WebXssEscapeFilterRule.java */ package com.zhgl.filter; import java.util.List; import com.zhgl.filter.defender.Defender; /** * <一句话功能简述> <功能详细描述> * * @author 姓名 工号 * @version [版本号, 2015-12-2] * @see [相关类/方法] * @since [产品/模块版本] */ public class WebXssEscapeFilterRule { /** * action名称 */ private String actionName; /** * 返回类型 */ private String resultType; /** * 拦截器 */ private Defender defender; /** * 拦截的参数名称 */ private List<String> params; public String getActionName() { return actionName; } public void setActionName(String actionName) { this.actionName = actionName; } public String getResultType() { return resultType; } public void setResultType(String resultType) { this.resultType = resultType; } public List<String> getParams() { return params; } public void setParams(List<String> params) { this.params = params; } public Defender getDefender() { return defender; } public void setDefender(Defender defender) { this.defender = defender; } }
4)、WebXssEscapeFilterUtil工具类
/* * 文 件 名: WebXssEscapeFilterUtil.java */ package com.zhgl.filter; /** * <一句话功能简述> <功能详细描述> * * @author 姓名 工号 * @version [版本号, 2015-12-2] * @see [相关类/方法] * @since [产品/模块版本] */ public class WebXssEscapeFilterUtil { private static WebXssEscapeFilterUtil xssUtil; private static WebXssEscapeFilterConfig config; private WebXssEscapeFilterUtil() { config = new WebXssEscapeFilterConfig(); } public static WebXssEscapeFilterUtil getInstance() { return xssUtil; } public WebXssEscapeFilterRule getFilterRule(String url,String actionName){ WebXssEscapeFilterRule urlRule = config.getUrlParamRule(url, actionName); return urlRule; } /* public String doFilter(String url, String actionName, String paramName, String value) { WebXssEscapeFilterRule urlRule = config.getUrlParamRule(url, actionName, paramName); if (urlRule == null) { return value; } return urlRule.getDefender().doFilter(value); }*/ static { try { xssUtil = new WebXssEscapeFilterUtil(); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } }
5、默认的过滤规则接口类Defender.java
/* * 文 件 名: Defender.java */ package com.zhgl.filter.defender; /** * <一句话功能简述> * <功能详细描述> * * @author 姓名 工号 * @version [版本号, 2015-12-2] * @see [相关类/方法] * @since [产品/模块版本] */ public abstract interface Defender { public abstract Boolean doFilter(String paramString); }
6、规则接口的实现类DefaultDefender.java
/* * 文 件 名: defaultDefender.java */ package com.zhgl.filter.defender; import java.util.regex.Pattern; /** * <一句话功能简述> * <功能详细描述> * * @author 姓名 工号 * @version [版本号, 2015-12-2] * @see [相关类/方法] * @since [产品/模块版本] */ public class DefaultDefender implements Defender { private static final String XSS_REGEX = ".*[[<>]+|[']+|[\"]+|[/]+|[&]+|[\\\\]+].*"; /** * 重载方法 * @param paramString * @return */ @Override public Boolean doFilter(String paramString) { Pattern pattern = Pattern.compile(XSS_REGEX); if(!pattern.matcher(paramString).matches()){ return true; }else{ return false; } } }
7、总结:实现思路主要通过过滤器将特殊字符进行过滤掉,在过滤器第一次执行的时候加载过滤规则并将保存到缓存中。