Spring MVC 防止XSS注入

余铭晨
2023-12-01

方法一:摘自https://www.cnblogs.com/yuanchaoyong/p/7243492.htmlFilter拦截

包装request->创建过滤器->添加过滤器

通过扩展HttpServletRequestWrapper,对HttpServletRequest进行二次包装,覆盖其 public String[] getParameterValues(String name) 方法,在此方法中对各个参数值进行XSS过滤(Spring MVC 部分解析是调用的此方法),实现方式:
1、XssAndSqlHttpServletRequestWrapper 类:

import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {

    HttpServletRequest orgRequest = null;

    public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        orgRequest = request;
    }

    /**
     * 覆盖getParameter方法,将参数名和参数值都做xss & sql过滤。<br/>
     * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
     * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(xssEncode(name));
        if (value != null) {
            value = xssEncode(value);
        }
        return value;
    }

    /**
     * 覆盖getHeader方法,将参数名和参数值都做xss & sql过滤。<br/>
     * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
     * getHeaderNames 也可能需要覆盖
     */
    @Override
    public String getHeader(String name) {

        String value = super.getHeader(xssEncode(name));
        if (value != null) {
            value = xssEncode(value);
        }
        return value;
    }

    /**
     * 将容易引起xss & sql漏洞的半角字符直接替换成全角字符
     *
     * @param s
     * @return
     */
    private static String xssEncode(String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }else{
            s = stripXSSAndSql(s);
        }
        StringBuilder sb = new StringBuilder(s.length() + 16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '>':
                    sb.append(">");// 转义大于号
                    break;
                case '<':
                    sb.append("<");// 转义小于号
                    break;
                case '\'':
                    sb.append("'");// 转义单引号
                    break;
                case '\"':
                    sb.append(""");// 转义双引号
                    break;
                case '&':
                    sb.append("&");// 转义&
                    break;
                case '#':
                    sb.append("#");// 转义#
                    break;
                default:
                    sb.append(c);
                    break;
            }
        }
        return sb.toString();
    }

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

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

        return req;
    }

    /**
     *
     * 防止xss跨脚本攻击(替换,根据实际情况调整)
     */

    public static String stripXSSAndSql(String value) {
        if (value != null) {
            // NOTE: It's highly recommended to use the ESAPI library and
            // uncomment the following line to
            // avoid encoded attacks.
            // value = ESAPI.encoder().canonicalize(value);
            // Avoid null characters
            /**         value = value.replaceAll("", "");***/
            // Avoid anything between script tags
            Pattern scriptPattern = Pattern.compile("<[\r\n| | ]*script[\r\n| | ]*>(.*?)</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid anything in a src="http://www.yihaomen.com/article/java/..." type of e-xpression
//            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("</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Remove any lonesome <script ...> tag
            scriptPattern = Pattern.compile("<[\r\n| | ]*script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid eval(...) expressions
            scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid e-xpression(...) expressions
            scriptPattern = Pattern.compile("e-xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid javascript:... expressions
            scriptPattern = Pattern.compile("javascript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid vbscript:... expressions
            scriptPattern = Pattern.compile("vbscript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid onload= expressions
            scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
        }
        return value;
    }

}

2、XssAndSqlFilter 类: 

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 XssAndSqlFilter implements Filter {

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

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        XssAndSqlHttpServletRequestWrapper xssRequest = new XssAndSqlHttpServletRequestWrapper((HttpServletRequest) request);
        chain.doFilter(xssRequest, response);
    }

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

    }

}

3、web.xml配置

  <!-- 解决xss & sql漏洞 -->
    <filter>
        <filter-name>xssAndSqlFilter</filter-name>
        <filter-class>com.**.commons.filter.XssAndSqlFilter</filter-class>
    </filter>
    <!-- 解决xss & sql漏洞 -->
    <filter-mapping>
        <filter-name>xssAndSqlFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>

 亲测情况:利用easyui的form post提交有效,调用方式:

<form id="resDataAddForm" method="post" enctype="multipart/form-data">

$('#resDataAddForm').form({...
但是当下面的情况时失效,
    <form id="onlinelyAddForm" method="post" class="layui-form">
 

form.on('submit(demo1)', function(data){
		 var data = data.field;
     	$.ajax({
		    type:"post",
	 	    url:"${path}/onlinely/add",
	 	    data:{"name":data.name,"p_reply_email":data.p_reply_email,"p_content":data.p_content},
		    dataType:"json",
		    async:false,
            success: function (result) {
                    ....

方法二:摘自https://blog.csdn.net/zhuangnet/article/details/78744004,为防止删博客,移植过来。

主要思路是:在Spring MVC调用Controller前,通过动态代理和反射机制对Controller的调用进行拦截,并在挡截中对Mehtod参数的值进行XSS过滤替换。

1、 HandlerExecutionChainWrapper.java



import org.springframework.beans.factory.BeanFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.util.HtmlUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by zhanghuaiyu on 2019/6/14.
 */
public class HandlerExecutionChainWrapper extends HandlerExecutionChain {

    private BeanFactory beanFactory;
    private HttpServletRequest request;
    private HandlerMethod handlerWrapper;
    private byte[] lock = new byte[0];

    public HandlerExecutionChainWrapper(HandlerExecutionChain chain,
                                        HttpServletRequest request,
                                        BeanFactory beanFactory) {
        super(chain.getHandler(),chain.getInterceptors());
        this.request = request;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object getHandler() {
        if (handlerWrapper != null) {
            return handlerWrapper;
        }

        synchronized (lock) {
            if (handlerWrapper != null) {
                return handlerWrapper;
            }
            HandlerMethod superMethodHandler = (HandlerMethod)super.getHandler();
            Object proxyBean = createProxyBean(superMethodHandler);
            handlerWrapper = new HandlerMethod(proxyBean,superMethodHandler.getMethod());
            return handlerWrapper;
        }

    }

    /**
     * 为Controller Bean创建一个代理实例,以便用于 实现调用真实Controller Bean前的切面拦截
     * 用以过滤方法参数中可能的XSS注入
     * @param handler
     * @return
     */
    private Object createProxyBean(HandlerMethod handler) {
        try {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(handler.getBeanType());
            Object bean = handler.getBean();
            if (bean instanceof String) {
                bean = beanFactory.getBean((String)bean);
            }
            ControllerXssInterceptor xss = new ControllerXssInterceptor(bean);
            xss.setRequest(this.request);
            enhancer.setCallback(xss);
            return enhancer.create();
        }catch(Exception e) {
            throw new IllegalStateException("为Controller创建代理失败:"+e.getMessage(), e);
        }
    }


    public static class ControllerXssInterceptor implements MethodInterceptor {

        private Object target;
        private HttpServletRequest request;
        private List<String> objectMatchPackages;

        public ControllerXssInterceptor(Object target) {
            this.target = target;
            this.objectMatchPackages = new ArrayList<String>();
            this.objectMatchPackages.add("com.xx");
        }

        public void setRequest(HttpServletRequest request) {
            this.request = request;
        }


        @Override
        public Object intercept(Object obj, Method method, Object[] args,
                                MethodProxy proxy)
                throws Throwable {

            //对Controller的方法参数进行调用前处理
            //过滤String类型参数中可能存在的XSS注入
            if (args != null) {
                for (int i=0;i<args.length;i++) {
                    if (args[i]==null)
                        continue;

                    if (args[i] instanceof String) {
                        args[i] = stringXssReplace((String)args[i]);
                        continue;
                    }

                    for(String pk:objectMatchPackages) {
                        if (args[i].getClass().getName().startsWith(pk)) {
                            objectXssReplace(args[i]);
                            break;
                        }
                    }
                }
            }
            return method.invoke(target, args);
        }

        private String stringXssReplace(String argument) {
            return HtmlUtils.htmlEscape(argument);
        }

        private void objectXssReplace(final Object argument) {
            if (argument == null)
                return;

            ReflectionUtils.doWithFields(argument.getClass(), new ReflectionUtils.FieldCallback(){

                @Override
                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                    ReflectionUtils.makeAccessible(field);
                    String fv = (String)field.get(argument);
                    if (fv != null) {
                        String nv = HtmlUtils.htmlEscape(fv);
                        field.set(argument, nv);
                    }
                }

            }, new ReflectionUtils.FieldFilter(){

                @Override
                public boolean matches(Field field) {
                    boolean typeMatch = String.class.equals(field.getType());

                    if (request!=null && "GET".equals(request.getMethod())) {
                        boolean requMatch = request.getParameterMap().containsKey(field.getName());
                        return typeMatch && requMatch;
                    }

                    return typeMatch;
                }

            });
        }
    }


}

2、DispatcherServletWrapper.java



import org.springframework.stereotype.Controller;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerExecutionChain;

import javax.servlet.http.HttpServletRequest;

@SuppressWarnings("serial")
public class DispatcherServletWrapper extends DispatcherServlet {

    @Override
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        HandlerExecutionChain chain = super.getHandler(request);
        Object handler = chain.getHandler();
        if (!(handler instanceof HandlerMethod)) {
            return chain;
        }

        HandlerMethod hm = (HandlerMethod)handler;
        if (!hm.getBeanType().isAnnotationPresent(Controller.class)) {
            return chain;
        }

        //本扩展仅处理@Controller注解的Bean
        return new HandlerExecutionChainWrapper(chain,request,getWebApplicationContext());
    }

}

 3、web.xml

 <servlet>
        <servlet-name>spring-mvc</servlet-name>
        <servlet-class>com.xxxx.commons.filter.DispatcherServletWrapper</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
        <multipart-config>
            <location>/</location>
            <max-file-size>524288000</max-file-size>
            <max-request-size>2097152000</max-request-size>
            <file-size-threshold>0</file-size-threshold>
        </multipart-config>
    </servlet>

亲测上面的问题解决了,但是第一种方式也没删掉。没有全面测试,只是把第一种方式的问题解决了。理论上应该是没有问题的

 类似资料: