http://winnie825.iteye.com/blog/1170833
先说一下实现思路:
1. 使用正则表达式的方式实现脚本过滤,这个方法准确率较高,但是可能根据不能的要求会变动;
2. 为了保证配置灵活(包括正则表达式灵活),使用xml配置文件的方式记录配置信息,配置信息包含是否开启校验、是否记录日志、是否中断请求、是否替换脚本字符等;
3. 为保证xml与正则表达式的特殊字符不冲突,使用<![CDATA[]]>标签存放正则表达式,但是在类中需要特殊处理;
4. 通过继承HttpRequestWrapper的方式实现request中header和parameter信息过滤;
5. xml解析使用dom4j,稍后会对这个工具的使用写一篇文章,暂时辛苦大家去网站查找资料(这篇文章很不错http://www.ibm.com/developerworks/cn/xml/x-dom4j.html);
6. 使用XSSSecurityManager类实现配置信息加载和处理,XSSSecurityConfig记录匹配信息,XSSSecurityCon标识程序所需常量;
- package com.sg.security;
- import java.io.IOException;
- import java.util.Enumeration;
- import java.util.Map;
- import java.util.Set;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletRequestWrapper;
- import javax.servlet.http.HttpServletResponse;
- /**
- * @author winnie
- * @date
- * @describe request信息封装类,用于判断、处理request请求中特殊字符
- */
- public class XSSHttpRequestWrapper extends HttpServletRequestWrapper {
- /**
- * 封装http请求
- * @param request
- */
- public XSSHttpRequestWrapper(HttpServletRequest request) {
- super(request);
- }
- @Override
- public String getHeader(String name) {
- String value = super.getHeader(name);
- // 若开启特殊字符替换,对特殊字符进行替换
- if(XSSSecurityConfig.REPLACE){
- XSSSecurityManager.securityReplace(name);
- }
- return value;
- }
- @Override
- public String getParameter(String name) {
- String value = super.getParameter(name);
- // 若开启特殊字符替换,对特殊字符进行替换
- if(XSSSecurityConfig.REPLACE){
- XSSSecurityManager.securityReplace(name);
- }
- return value;
- }
- /**
- * 没有违规的数据,就返回false;
- *
- * @return
- */
- @SuppressWarnings("unchecked")
- private boolean checkHeader(){
- Enumeration<String> headerParams = this.getHeaderNames();
- while(headerParams.hasMoreElements()){
- String headerName = headerParams.nextElement();
- String headerValue = this.getHeader(headerName);
- if(XSSSecurityManager.matches(headerValue)){
- return true;
- }
- }
- return false;
- }
- /**
- * 没有违规的数据,就返回false;
- *
- * @return
- */
- @SuppressWarnings("unchecked")
- private boolean checkParameter(){
- Map<String,Object> submitParams = this.getParameterMap();
- Set<String> submitNames = submitParams.keySet();
- for(String submitName : submitNames){
- Object submitValues = submitParams.get(submitName);
- if(submitValues instanceof String){
- if(XSSSecurityManager.matches((String)submitValues)){
- return true;
- }
- }else if(submitValues instanceof String[]){
- for(String submitValue : (String[])submitValues){
- if(XSSSecurityManager.matches((String)submitValue)){
- return true;
- }
- }
- }
- }
- return false;
- }
- /**
- * 没有违规的数据,就返回false;
- * 若存在违规数据,根据配置信息判断是否跳转到错误页面
- * @param response
- * @return
- * @throws IOException
- * @throws ServletException
- */
- public boolean validateParameter(HttpServletResponse response) throws ServletException, IOException{
- // 开始header校验,对header信息进行校验
- if(XSSSecurityConfig.IS_CHECK_HEADER){
- if(this.checkHeader()){
- return true;
- }
- }
- // 开始parameter校验,对parameter信息进行校验
- if(XSSSecurityConfig.IS_CHECK_PARAMETER){
- if(this.checkParameter()){
- return true;
- }
- }
- return false;
- }
- }
- /**
- *
- */
- package com.sg.security;
- 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;
- import org.apache.log4j.Logger;
- /**
- * @author winnie
- * @date
- * @describe 安全信息审核类
- */
- public class XSSSecurityFilter implements Filter{
- private static Logger logger = Logger.getLogger(XSSSecurityFilter.class);
- /**
- * 销毁操作
- */
- public void destroy() {
- logger.info("XSSSecurityFilter destroy() begin");
- XSSSecurityManager.destroy();
- logger.info("XSSSecurityFilter destroy() end");
- }
- /**
- * 安全审核
- * 读取配置信息
- */
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- // 判断是否使用HTTP
- checkRequestResponse(request, response);
- // 转型
- HttpServletRequest httpRequest = (HttpServletRequest) request;
- HttpServletResponse httpResponse = (HttpServletResponse) response;
- // http信息封装类
- XSSHttpRequestWrapper xssRequest = new XSSHttpRequestWrapper(httpRequest);
- // 对request信息进行封装并进行校验工作,若校验失败(含非法字符),根据配置信息进行日志记录和请求中断处理
- if(xssRequest.validateParameter(httpResponse)){
- if(XSSSecurityConfig.IS_LOG){
- // 记录攻击访问日志
- // 可使用数据库、日志、文件等方式
- }
- if(XSSSecurityConfig.IS_CHAIN){
- httpRequest.getRequestDispatcher(XSSSecurityCon.FILTER_ERROR_PAGE).forward( httpRequest, httpResponse);
- return;
- }
- }
- chain.doFilter(xssRequest, response);
- }
- /**
- * 初始化操作
- */
- public void init(FilterConfig filterConfig) throws ServletException {
- XSSSecurityManager.init(filterConfig);
- }
- /**
- * 判断Request ,Response 类型
- * @param request
- * ServletRequest
- * @param response
- * ServletResponse
- * @throws ServletException
- */
- private void checkRequestResponse(ServletRequest request,
- ServletResponse response) throws ServletException {
- if (!(request instanceof HttpServletRequest)) {
- throw new ServletException("Can only process HttpServletRequest");
- }
- if (!(response instanceof HttpServletResponse)) {
- throw new ServletException("Can only process HttpServletResponse");
- }
- }
- }
- /**
- *
- */
- package com.sg.security;
- import java.util.Iterator;
- import java.util.regex.Pattern;
- import javax.servlet.FilterConfig;
- import org.apache.log4j.Logger;
- import org.dom4j.DocumentException;
- import org.dom4j.Element;
- import org.dom4j.io.SAXReader;
- /**
- * @author winnie
- * @date
- * @describe 安全过滤配置管理类,由XSSSecurityManger修改
- */
- public class XSSSecurityManager {
- private static Logger logger = Logger.getLogger(XSSSecurityManager.class);
- /**
- * REGEX:校验正则表达式
- */
- public static String REGEX;
- /**
- * 特殊字符匹配
- */
- private static Pattern XSS_PATTERN ;
- private XSSSecurityManager(){
- //不可被实例化
- }
- public static void init(FilterConfig config){
- logger.info("XSSSecurityManager init(FilterConfig config) begin");
- //初始化过滤配置文件
- String xssPath = config.getServletContext().getRealPath("/")
- + config.getInitParameter("securityconfig");
- // 初始化安全过滤配置
- try {
- if(initConfig(xssPath)){
- // 生成匹配器
- XSS_PATTERN = Pattern.compile(REGEX);
- }
- } catch (DocumentException e) {
- logger.error("安全过滤配置文件xss_security_config.xml加载异常",e);
- }
- logger.info("XSSSecurityManager init(FilterConfig config) end");
- }
- /**
- * 读取安全审核配置文件xss_security_config.xml
- * 设置XSSSecurityConfig配置信息
- * @param path 配置文件地址 eg C:/apache-tomcat-6.0.33/webapps/security_filter/WebRoot/config/xss/xss_security_config.xml
- * @return
- * @throws DocumentException
- */
- @SuppressWarnings("unchecked")
- public static boolean initConfig(String path) throws DocumentException {
- logger.info("XSSSecurityManager.initConfig(String path) begin");
- Element superElement = new SAXReader().read(path).getRootElement();
- XSSSecurityConfig.IS_CHECK_HEADER = new Boolean(getEleValue(superElement,XSSSecurityCon.IS_CHECK_HEADER));
- XSSSecurityConfig.IS_CHECK_PARAMETER = new Boolean(getEleValue(superElement,XSSSecurityCon.IS_CHECK_PARAMETER));
- XSSSecurityConfig.IS_LOG = new Boolean(getEleValue(superElement,XSSSecurityCon.IS_LOG));
- XSSSecurityConfig.IS_CHAIN = new Boolean(getEleValue(superElement,XSSSecurityCon.IS_CHAIN));
- XSSSecurityConfig.REPLACE = new Boolean(getEleValue(superElement,XSSSecurityCon.REPLACE));
- Element regexEle = superElement.element(XSSSecurityCon.REGEX_LIST);
- if(regexEle != null){
- Iterator<Element> regexIt = regexEle.elementIterator();
- StringBuffer tempStr = new StringBuffer("^");
- //xml的cdata标签传输数据时,会默认在\前加\,需要将\\替换为\
- while(regexIt.hasNext()){
- Element regex = (Element)regexIt.next();
- String tmp = regex.getText();
- tmp = tmp.replaceAll("\\\\\\\\", "\\\\");
- tempStr.append(tmp);
- tempStr.append("|");
- }
- if(tempStr.charAt(tempStr.length()-1)=='|'){
- REGEX= tempStr.substring(0, tempStr.length()-1)+"$";
- logger.info("安全匹配规则"+REGEX);
- }else{
- logger.error("安全过滤配置文件加载失败:正则表达式异常 "+tempStr.toString());
- return false;
- }
- }else{
- logger.error("安全过滤配置文件中没有 "+XSSSecurityCon.REGEX_LIST+" 属性");
- return false;
- }
- logger.info("XSSSecurityManager.initConfig(String path) end");
- return true;
- }
- /**
- * 从目标element中获取指定标签信息,若找不到该标签,记录错误日志
- * @param element 目标节点
- * @param tagName 制定标签
- * @return
- */
- private static String getEleValue(Element element, String tagName){
- if (isNullStr(element.elementText(tagName))){
- logger.error("安全过滤配置文件中没有 "+XSSSecurityCon.REGEX_LIST+" 属性");
- }
- return element.elementText(tagName);
- }
- /**
- * 对非法字符进行替换
- * @param text
- * @return
- */
- public static String securityReplace(String text){
- if(isNullStr(text)){
- return text;
- }else{
- return text.replaceAll(REGEX, XSSSecurityCon.REPLACEMENT);
- }
- }
- /**
- * 匹配字符是否含特殊字符
- * @param text
- * @return
- */
- public static boolean matches(String text){
- if(text==null){
- return false;
- }
- return XSS_PATTERN.matcher(text).matches();
- }
- /**
- * 释放关键信息
- */
- public static void destroy(){
- logger.info("XSSSecurityManager.destroy() begin");
- XSS_PATTERN = null;
- REGEX = null;
- logger.info("XSSSecurityManager.destroy() end");
- }
- /**
- * 判断是否为空串,建议放到某个工具类中
- * @param value
- * @return
- */
- public static boolean isNullStr(String value){
- return value == null || value.trim().equals("");
- }
- }
- /**
- *
- */
- package com.sg.security;
- /**
- * @author winnie
- * 安全过滤配置信息类
- */
- public class XSSSecurityConfig {
- /**
- * CHECK_HEADER:是否开启header校验
- */
- public static boolean IS_CHECK_HEADER;
- /**
- * CHECK_PARAMETER:是否开启parameter校验
- */
- public static boolean IS_CHECK_PARAMETER;
- /**
- * IS_LOG:是否记录日志
- */
- public static boolean IS_LOG;
- /**
- * IS_LOG:是否中断操作
- */
- public static boolean IS_CHAIN;
- /**
- * REPLACE:是否开启替换
- */
- public static boolean REPLACE;
- }
- /**
- *
- */
- package com.sg.security;
- /**
- * @author winnie
- * @date
- * @describe
- */
- public class XSSSecurityCon {
- /**
- * 配置文件标签 isCheckHeader
- */
- public static String IS_CHECK_HEADER = "isCheckHeader";
- /**
- * 配置文件标签 isCheckParameter
- */
- public static String IS_CHECK_PARAMETER = "isCheckParameter";
- /**
- * 配置文件标签 isLog
- */
- public static String IS_LOG = "isLog";
- /**
- * 配置文件标签 isChain
- */
- public static String IS_CHAIN = "isChain";
- /**
- * 配置文件标签 replace
- */
- public static String REPLACE = "replace";
- /**
- * 配置文件标签 regexList
- */
- public static String REGEX_LIST = "regexList";
- /**
- * 替换非法字符的字符串
- */
- public static String REPLACEMENT = "";
- /**
- * FILTER_ERROR_PAGE:过滤后错误页面
- */
- public static String FILTER_ERROR_PAGE = "/common/filtererror.jsp";
- }
xss_security_config.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <XSSConfig>
- <!-- 是否进行header校验 -->
- <isCheckHeader>false</isCheckHeader>
- <!-- 是否进行parameter校验 -->
- <isCheckParameter>true</isCheckParameter>
- <!-- 是否记录日志 -->
- <isLog>true</isLog>
- <!-- 是否中断请求 -->
- <isChain>false</isChain>
- <!-- 是否开启特殊字符替换 -->
- <replace>true</replace>
- <!-- 是否开启特殊url校验 -->
- <isCheckUrl>true</isCheckUrl>
- <regexList>
- <!-- 匹配含有字符: alert( ) -->
- <regex><![CDATA[.*[A|a][L|l][E|e][R|r][T|t]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: window.location = -->
- <regex><![CDATA[.*[W|w][I|i][N|n][D|d][O|o][W|w]\\.[L|l][O|o][C|c][A|a][T|t][I|i][O|o][N|n]\\s*=.*]]></regex>
- <!-- 匹配含有字符:style = x:ex pression ( ) -->
- <regex><![CDATA[.*[S|s][T|t][Y|y][L|l][E|e]\\s*=.*[X|x]:[E|e][X|x].*[P|p][R|r][E|e][S|s]{1,2}[I|i][O|o][N|n]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: document.cookie -->
- <regex><![CDATA[.*[D|d][O|o][C|c][U|u][M|m][E|e][N|n][T|t]\\.[C|c][O|o]{2}[K|k][I|i][E|e].*]]></regex>
- <!-- 匹配含有字符: eval( ) -->
- <regex><![CDATA[.*[E|e][V|v][A|a][L|l]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: unescape() -->
- <regex><![CDATA[.*[U|u][N|n][E|e][S|s][C|c][A|a][P|p][E|e]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: execscript( ) -->
- <regex><![CDATA[.*[E|e][X|x][E|e][C|c][S|s][C|c][R|r][I|i][P|p][T|t]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: msgbox( ) -->
- <regex><![CDATA[.*[M|m][S|s][G|g][B|b][O|o][X|x]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: confirm( ) -->
- <regex><![CDATA[.*[C|c][O|o][N|n][F|f][I|i][R|r][M|m]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: prompt( ) -->
- <regex><![CDATA[.*[P|p][R|r][O|o][M|m][P|p][T|t]\\s*\\(.*\\).*]]></regex>
- <!-- 匹配含有字符: <script> </script> -->
- <regex><![CDATA[.*<[S|s][C|c][R|r][I|i][P|p][T|t]>.*</[S|s][C|c][R|r][I|i][P|p][T|t]>.*]]></regex>
- <!-- 匹配含有字符: 含有一个符号: " -->
- <regex><![CDATA[[.&[^\"]]*\"[.&[^\"]]*]]></regex>
- <!-- 匹配含有字符: 含有一个符号: ' -->
- <regex><![CDATA[[.&[^']]*'[.&[^']]*]]></regex>
- <!-- 匹配含有字符: 含有回车换行 和 <script> </script> -->
- <regex><![CDATA[[[.&[^a]]|[|a|\n|\r\n|\r|\u0085|\u2028|\u2029]]*<[S|s][C|c][R|r][I|i][P|p][T|t]>.*</[S|s][C|c][R|r][I|i][P|p][T|t]>[[.&[^a]]|[|a|\n|\r\n|\r|\u0085|\u2028|\u2029]]*]]></regex>
- </regexList>
- </XSSConfig>
web.xml配置
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app version="2.5"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
- <welcome-file-list>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
- <!-- 信息安全审核 -->
- <filter>
- <filter-name>XSSFiler</filter-name>
- <filter-class>
- com.sg.security.XSSSecurityFilter
- </filter-class>
- <init-param>
- <param-name>securityconfig</param-name>
- <param-value>
- /WebRoot/config/xss/xss_security_config.xml
- </param-value>
- </init-param>
- </filter>
- <!-- 拦截请求类型 -->
- <filter-mapping>
- <filter-name>XSSFiler</filter-name>
- <url-pattern>*.jsp</url-pattern>
- </filter-mapping>
- <filter-mapping>
- <filter-name>XSSFiler</filter-name>
- <url-pattern>*.do</url-pattern>
- </filter-mapping>
- </web-app>