小提莫来报到
美好的事情值得等待

概念

XSS攻击全称跨站脚本攻击,是为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS,XSS是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。

项目环境

spring + struts2 +.....(仅列举相关的)

需求

防止xss攻击

分析

1.防止xss攻击,可以从请求处拦截特殊字符,核心是过滤特殊字符串

2.由于项目是采用struts2来处理请求的,所以应从struts处着手找方案

3.struts2中的StrutsPrepareAndExecuteFilter,核心是一个Filter,Action可以脱离web容器,让http请求和action关联在一起的

4.可以继承StrutsPrepareAndExecuteFilter类来实现特殊字符过滤

StrutsPrepareAndExecuteFilter分析

 StrutsPrepareAndExecuteFilter与普通的Filter并无区别,方法除继承自Filter外,仅有一个回调方法,Filter方法调用顺序是init—>doFilter—>destroy

注意:只关心怎么实现可以跳过这个内容,直接看下面实现部分

 1.init方法

   init是Filter第一个运行的方法,主要的工作是初始化一些配置

public void init(FilterConfig filterConfig) throws ServletException {
    InitOperations init = new InitOperations();
    Dispatcher dispatcher = null;
    try {
        //封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中
        FilterHostConfig config = new FilterHostConfig(filterConfig);
        // 初始化struts内部日志  
        init.initLogging(config);
        //创建dispatcher ,加载资源
        dispatcher = init.initDispatcher(config);
        init.initStaticContentLoader(config, dispatcher);
        //初始化类属性:prepare 、execute
        this.prepare = new PrepareOperations(dispatcher);
        this.execute = new ExecuteOperations(dispatcher);
        this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
        //回调空的postInit方法  
        this.postInit(dispatcher, filterConfig);
    } finally {
        if (dispatcher != null) {
            dispatcher.cleanUpAfterInit();
        }
        init.cleanup();
    }
}    

 1-1.FilterHostConfig类

  可以看出,FilterHostConfig主要是对配置文件的一个封装,如果有需求要操作配置文件的参数,可以继承此类实现我们的业务

public class FilterHostConfig implements HostConfig {
    private FilterConfig config;
     /** 
     *构造函数   
     */  
    public FilterHostConfig(FilterConfig config) {
        this.config = config;
    }
    /** 
     *  根据init-param配置的param-name获取param-value的值 
     */
    public String getInitParameter(String key) {
        return this.config.getInitParameter(key);
    }
    /** 
     *  返回初始化参数名的List 
     */ 
    public Iterator<String> getInitParameterNames() {
        return MakeIterator.convert(this.config.getInitParameterNames());
    }
    /** 
     *  返回上下文
     */ 
    public ServletContext getServletContext() {
        return this.config.getServletContext();
    }
}

1-2.初始化Dispatcher

    创建Dispatcher,会读取 filterConfig 中的配置信息

public Dispatcher initDispatcher(HostConfig filterConfig) {
        Dispatcher dispatcher = this.createDispatcher(filterConfig);
        dispatcher.init();
        return dispatcher;
}

Dispatcher类init的源码

public void init() {
    if (this.configurationManager == null) {
        this.configurationManager = this
                .createConfigurationManager("struts");
    }

    try {
        
        this.init_FileManager();
        //加载org/apache/struts2/default.properties  
        this.init_DefaultProperties();
        //加载struts-default.xml,struts-plugin.xml,struts.xml 
        this.init_TraditionalXmlConfigurations();
        this.init_LegacyStrutsProperties();
        //用户自己实现的ConfigurationProviders类     
        this.init_CustomConfigurationProviders();
        //Filter的初始化参数  
        this.init_FilterInitParameters();
        this.init_AliasStandardObjects();
        Container ex = this.init_PreloadConfiguration();
        ex.inject(this);
        this.init_CheckWebLogicWorkaround(ex);
        if (!dispatcherListeners.isEmpty()) {
            Iterator i$ = dispatcherListeners.iterator();

            while (i$.hasNext()) {
                DispatcherListener l = (DispatcherListener) i$.next();
                l.dispatcherInitialized(this);
            }
        }

        this.errorHandler.init(this.servletContext);
    } catch (Exception arg3) {
        if (LOG.isErrorEnabled()) {
            LOG.error("Dispatcher initialization failed", arg3,
                    new String[0]);
        }

        throw new StrutsException(arg3);
    }
}
View Code

 1-3.创建Dispatcher

  将配置信息解析出来,封装成为一个Map,然后根据servlet上下文和参数Map构造Dispatcher 

private Dispatcher createDispatcher(HostConfig filterConfig) {
        HashMap params = new HashMap();
        Iterator e = filterConfig.getInitParameterNames();
        while (e.hasNext()) {
            String name = (String) e.next();
            String value = filterConfig.getInitParameter(name);
            params.put(name, value);
        }
        return new Dispatcher(filterConfig.getServletContext(), params);
    }

 2.doFilter方法

public void doFilter(ServletRequest req, ServletResponse res,
        FilterChain chain) throws IOException, ServletException {
    //父类向子类转:强转为http请求、响应
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
    try {
        if (this.excludedPatterns != null
                && this.prepare.isUrlExcluded(request,
                        this.excludedPatterns)) {
            chain.doFilter(request, response);
        } else {
            //设置编码和国际化  
            this.prepare.setEncodingAndLocale(request, response);
            //创建Action上下文
            this.prepare.createActionContext(request, response);
            this.prepare.assignDispatcherToThread();
            request = this.prepare.wrapRequest(request);
            ActionMapping mapping = this.prepare.findActionMapping(request,
                    response, true);
            if (mapping == null) {
                boolean handled = this.execute
                        .executeStaticResourceRequest(request, response);
                if (!handled) {
                    chain.doFilter(request, response);
                }
            } else {
                this.execute.executeAction(request, response, mapping);
            }
        }
    } finally {
        this.prepare.cleanupRequest(request);
    }

}

2-1.setEncodingAndLocale

  设置编码这是调用了Dispatcherde.prepare方法加载配置

public void setEncodingAndLocale(HttpServletRequest request,
            HttpServletResponse response) {
        this.dispatcher.prepare(request, response);
    }

prepare方法只是做一些初始化和国际化的缺省设置

public void prepare(HttpServletRequest request, HttpServletResponse response) {
    String encoding = null;
    if (this.defaultEncoding != null) {
        encoding = this.defaultEncoding;
    }

    if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
        encoding = "UTF-8";
    }

    Locale locale = null;
    if (this.defaultLocale != null) {
        locale = LocalizedTextUtil.localeFromString(this.defaultLocale,
                request.getLocale());
    }

    if (encoding != null) {
        this.applyEncoding(request, encoding);
    }

    if (locale != null) {
        response.setLocale(locale);
    }

    if (this.paramsWorkaroundEnabled) {
        request.getParameter("foo");
    }

}
View Code

2-2.createActionContext

   ActionContext是一个容器,这个容易主要存储request、session、application、parameters等

ActionContext是一个线程的本地变量,不同的action之间不会共享ActionContext,所以也不用考虑线程安全问题。

实质是一个Map,key是标示request、session、……的字符串,值是其对应的对象

private static ThreadLocal<Dispatcher> instance = new ThreadLocal();
protected Map<String, String> initParams;

创建Action上下文,初始化thread local 

public ActionContext createActionContext(HttpServletRequest request,
        HttpServletResponse response) {
    Integer counter = Integer.valueOf(1);
    Integer oldCounter = (Integer) request
            .getAttribute("__cleanup_recursion_counter");
    if (oldCounter != null) {
        counter = Integer.valueOf(oldCounter.intValue() + 1);
    }
    //从ThreadLocal中获取此ActionContext变量  
    ActionContext oldContext = ActionContext.getContext();
    ActionContext ctx;
    if (oldContext != null) {
        ctx = new ActionContext(new HashMap(oldContext.getContextMap()));
    } else {
        ValueStack stack = ((ValueStackFactory) this.dispatcher
                .getContainer().getInstance(ValueStackFactory.class))
                .createValueStack();
        stack.getContext().putAll(
                this.dispatcher.createContextMap(request, response,
                        (ActionMapping) null));
        //stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext
        ctx = new ActionContext(stack.getContext());
    }

    request.setAttribute("__cleanup_recursion_counter", counter);
    //将ActionContext存到ThreadLocal<ActionContext> actionContext  
    ActionContext.setContext(ctx);
    return ctx;
}

createContextMap方法

public Map<String, Object> createContextMap(HttpServletRequest request,
        HttpServletResponse response, ActionMapping mapping) {
    RequestMap requestMap = new RequestMap(request);
    HashMap params = new HashMap(request.getParameterMap());
    SessionMap session = new SessionMap(request);
    ApplicationMap application = new ApplicationMap(this.servletContext);
    //requestMap、params、session等Map封装成为一个上下文Map,逐个调用了map.put(Map p). 
    HashMap extraContext = this.createContextMap(requestMap, params,
            session, application, request, response);
    if (mapping != null) {
        extraContext.put("struts.actionMapping", mapping);
    }
    return extraContext;
}

executeAction方法

  封装执行的上下文环境,主要将相关信息存储入map,根据执行上下文参数,命名空间,名称等创建用户自定义Action的代理对象,执行execute方法,并转向结果  

public void executeAction(HttpServletRequest request,
            HttpServletResponse response, ActionMapping mapping)
            throws ServletException {
       this.dispatcher.serviceAction(request, response, mapping);
}

  看到这,会有一种不知所措的感觉,毕竟源码都是毕竟乏味的,我整理了张关系图如下,后面我们要实现的就是重写StrutsPrepareAndExecuteFilter来管理

重写并添加了自己规则的Dispatcher,也就是执行者,这里,我举个现实中关系的例子:

 

FilterHostConfig:开发的规范
Dispatcher:程序员
InitOperations:项目经理
StrutsPrepareAndExecuteFilter:老板

程序员要严格按照规范来写代码,项目经理要负责跟进程序员的整个工作过程,项目经理则只需对老板负责,定期汇报

上面的源码就是我按照这种关系分析的,这样比较好理解,关系就一目了然

(提示:如果图片看不清,可以右击保存到桌面查看)

 

执行者管理器的流程是这样的,后面实现部分就是着重修改这里,添加字符的拦截规则

(提示:如果图片看不清,可以右击保存到桌面查看)

实现

继承StrutsPrepareAndExecuteFilter类,重写init方法,在web.xml引用这个filter

 @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new InitOperations();
        Dispatcher dispatcher = null;
        try {
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            init.initLogging(config);
            dispatcher = initDispatcher(config);
            init.initStaticContentLoader(config, dispatcher);

            prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
            execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

            postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
    }

   
    public Dispatcher initDispatcher(HostConfig filterConfig) {
        Dispatcher dispatcher = createDispatcher(filterConfig);
        dispatcher.init();
        return dispatcher;
    }

   
    private Dispatcher createDispatcher(HostConfig filterConfig) {
        Map<String, String> params = new HashMap<String, String>();
        for (Iterator e = filterConfig.getInitParameterNames(); e.hasNext();) {
            String name = (String) e.next();
            String value = filterConfig.getInitParameter(name);
            params.put(name, value);
        }
        return new MDispatcher(filterConfig.getServletContext(), params);
    }

继承Dispatcher实现自己的MDispatcher重写createContextMap方法

@Override
    public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
            ActionMapping mapping) {

        // request map wrapping the http request objects
        Map requestMap = new RequestMap(request);
        
        //对参数进行xss过滤
        Map<String, String[]> paramMap = xssFilterParam(request); 

        // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
        Map params = new HashMap(paramMap);

        // session map wrapping the http session
        Map session = new SessionMap(request);

        // application map wrapping the ServletContext
        Map application = new ApplicationMap(servletContext);

        Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response);

        if (mapping != null) {
            extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
        }
        return extraContext;
    }

xssFilterParam

private Map<String, String[]> xssFilterParam(HttpServletRequest request) {
        Map<String, String[]> paramMap = new HashMap<String, String[]>();
        Map<String, String[]> reqParamMap = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : reqParamMap.entrySet()) {   
            String key = entry.getKey();
            LOG.debug("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
            if(!key.endsWith("$HTML")){
                //过滤
                String[] values = getParameterValues(request,key);
                paramMap.put(key, values);
            }else{
                //如果以   “$HTML” 结尾的参数,不进行过滤,并且构造新参数值
                String keyNew = key.substring(0,key.length()-5);
                paramMap.put(keyNew, entry.getValue());
                paramMap.put(key, entry.getValue());
            }
            
        }
        return paramMap;
    }

getParameterValues

private String[] getParameterValues(HttpServletRequest request,String name) {
        name = XssShieldUtil.stripXss(name);
        // 返回值之前 先进行过滤
        String[] values = request.getParameterValues(name);
        if(values != null){
            for (int i = 0; i < values.length; i++) {
                values[i] = XssShieldUtil.stripXss(values[i]);
            }
        }
        return values;
    }

我的xss字符处理工具类

public class XssShieldUtil {

    private static List<Pattern> patterns = null;

    private static List<Object[]> getXssPatternList() {
        List<Object[]> ret = new ArrayList<Object[]>();
        ret.add(new Object[]{"<(no)?script[^>]*>.*?</(no)?script>", Pattern.CASE_INSENSITIVE});
        ret.add(new Object[]{"eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
        ret.add(new Object[]{"expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
        ret.add(new Object[]{"(javascript:|vbscript:|view-source:)*", Pattern.CASE_INSENSITIVE});
        ret.add(new Object[]{"<(\"[^\"]*\"|\'[^\']*\'|[^\'\">])*>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
        ret.add(new Object[]{"(window\\.location|window\\.|\\.location|document\\.cookie|document\\.|alert\\(.*?\\)|window\\.open\\()*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
        ret.add(new Object[]{"<+\\s*\\w*\\s*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondragstart|ondrop|onerror=|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|onselectstart|onstart|onstop|onsubmit|onunload)+\\s*=+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL});
        return ret;
    }

    private static List<Pattern> getPatterns() {

        if (patterns == null) {

            List<Pattern> list = new ArrayList<Pattern>();

            String regex = null;
            Integer flag = null;
            int arrLength = 0;

            for(Object[] arr : getXssPatternList()) {
                arrLength = arr.length;
                for(int i = 0; i < arrLength; i++) {
                    regex = (String)arr[0];
                    flag = (Integer)arr[1];
                    list.add(Pattern.compile(regex, flag));
                }
            }

            patterns = list;
        }

        return patterns;
    }

    public static String stripXss(String value) {
        if(StringUtils.isNotBlank(value)) {

            Matcher matcher = null;

            for(Pattern pattern : getPatterns()) {
                matcher = pattern.matcher(value);
                // 匹配
                if(matcher.find()) {
                    // 删除相关字符串
                    value = matcher.replaceAll("");
                }
            }

            value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        }
        return value;
    }
}

最后在web.xml引用这个StrutsPrepareAndExecuteFilter

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>MStrutsPrepareAndExecuteFilter</filter-class>
</filter>

 

posted on 2017-08-16 15:10  小提莫来报到  阅读(13697)  评论(1编辑  收藏  举报