本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(1)

XSS和sql注入拦截器+请求包装

发布于2020-11-19 20:46     阅读(1571)     评论(0)     点赞(9)     收藏(2)


背景:

项目中经常遇到sql注入和XSS攻击,此时我们需要编写拦截器来对前台上传参数进行合法性校验。下面为从请求中读取参数的一些规则:

  • 1.application/x- www-form-urlencoded是post和form表单默认的类型。Servlet API规范中对该类型的请求内容提供了request.getParameter() 方法来获取请求参数值(其他类型的不可以调用此方法)。
  • 2.当请求内容不是application/x- www-form-urlencoded类型时,需要调用request.getInputStream()或request.getReader()方法来获取请求内容值。
  • 3.当请求体内容(get请求没有请求体)类型是application/x- www-form-urlencoded时,并且还没有调用过request.getParameter() 也可以直接调用request.getInputStream()或request.getReader()方法获取到请求内容再解析出具体都参数
  • 4.request.getParameter() 可重复调用,request.getInputStream()或request.getReader() 在上述三种任意一种调用后不可再次调用。
  • 5.request.getInputStream()返回请求内容字节流,多用于文件上传,request.getReader()是对前者返回内容的封装,可以让调用者更方便字符内容的处理(不用自己先获取字节流再做字符流的转换操作)
    从上面可以看出对于某些从流中读取参数的接口参数是不可重复读取的,所以对于此类在拦截器中读取参数进行校验后,在接口中就无法获取到参数,为了解决此问题,我们对需要请求进行包装。

包装请求过滤器

首先我们需要一个过滤器在过滤器中对请求进行包装并替换到过滤链中,我们把这个过滤器配置为第一个过滤器,这样在后面的过滤器或拦截器中就可以放心的进行参数读取
RequestWrapperFilter。java

package until;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @Description:    包装请求对象过滤器,方便拦截器可重复的读取包装对象克隆的参数
 * 后面拦截器或过滤器 获取到的请求体类型为MultipartHttpServletRequest 或者RequestWrapper 可进行参数读取
*/
public class RequestWrapperFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	
	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
	    ServletRequest requestWrapper = null;
	    if(servletRequest instanceof HttpServletRequest) {
	        HttpServletRequest request = (HttpServletRequest) servletRequest;
	        if (request.getContentType() != null && request.getContentType().contains("multipart/form-data")) { // 文件类型
	            MultipartResolver resolver = new CommonsMultipartResolver(request.getSession().getServletContext());
	            MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);
	            request = multipartRequest;
	            filterChain.doFilter(request, servletResponse); // 将转化后的 request 放入过滤链中
	        }else{ // 非文件类型包装
	            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
	            filterChain.doFilter(requestWrapper, servletResponse);
	        }
	    }else{
	        filterChain.doFilter(servletRequest, servletResponse);
	    }
	}

	@Override
	public void destroy() {
	
	}
}

配置包装请求过滤器

web.xml

<!-- 包装请求过滤器 -->
<filter>
    <filter-name>requestWrapperFilter</filter-name>
    <filter-class>until.RequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>requestWrapperFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

请求包装

RequestWrapper.java

package until;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * @Description:   包装请求
 * 1.拦截器使用
 * .拦截器接受RequestWrapperFilter包装后的请求 
*/
public class RequestWrapper extends HttpServletRequestWrapper {
    /**
     * 日志对象
     */
    private Logger logger = Logger.getLogger(this.getClass());
    private final String body;
    private String code ="utf-8";
    private Map<String, String[]> parameterMap = new HashMap<>(); // 所有参数的Map集合

    public RequestWrapper(HttpServletRequest request) throws UnsupportedEncodingException {
        super(request);
        StringBuffer stringBuffer = new StringBuffer();
        BufferedReader bufferedReader = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream,code));
                String readLine;
                while ((readLine = bufferedReader.readLine()) != null) {
                    stringBuffer.append(readLine);
                }
            } else {
                stringBuffer.append("");
            }
        } catch (IOException ex) {
            logger.error("RequestWrapper: 包装请求"+ ExceptionUtil.getStackTraceString(ex));
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                }catch (IOException e) {
                    logger.error("RequestWrapper: 包装请求 关闭流失败"+ ExceptionUtil.getStackTraceString(e));
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                }catch (IOException e) {
                    logger.error("RequestWrapper: 包装请求 关闭缓冲区失败"+ ExceptionUtil.getStackTraceString(e));
                }
            }
        }
        String body1 =stringBuffer.toString();
        body = StringUtil.urlCodeSpeciCharToStr(body1,code);
        if(StringUtils.isNotEmpty(body1)){
            String []paramArr = body1.split("&");
            if(paramArr != null && paramArr.length >0){
                for(String param : paramArr){
                    if(StringUtils.isNotEmpty(param)){
                        String [] keyValueArr = param.split("=");
                        if(keyValueArr != null ){
                            if(keyValueArr.length == 2 && StringUtils.isNotEmpty(keyValueArr[0])){
                                String arr[]={ StringUtil.urlCodeSpeciCharToStr(keyValueArr[1],code)};
                                parameterMap.put( StringUtil.urlCodeSpeciCharToStr(keyValueArr[0],code),arr);
                            }else{
                                String arr[]={ ""};
                                parameterMap.put( StringUtil.urlCodeSpeciCharToStr(keyValueArr[0],code),arr); // 有键为""无键为null
                            }
                        }
                    }
                }
            }
        }else{
            parameterMap = request.getParameterMap(); // get请求 从流中获取不到参数,需要以此方式获取参数
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(code));
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;

    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.body;
    }


    /**
     * 获取所有参数名
     * @return 返回所有参数名
     */
    @Override
    public Enumeration<String> getParameterNames() {
        Vector<String> vector = new Vector<String>(parameterMap.keySet());
        return vector.elements();
    }

    /**
     * 获取指定参数名的值,如果有重复的参数名,则返回第一个的值 接收一般变量 ,如text类型
     * @param name 指定参数名
     * @return 指定参数名的值
     */
    @Override
    public String getParameter(String name) {
        String[] results = parameterMap.get(name);
        if(results != null && results.length>0){
            return results[0];
        }
        return null;
    }


    /**
     * 获取指定参数名的所有值的数组,如:checkbox的所有数据
     * 接收数组变量 ,如checkobx类型
     */
    @Override
    public String[] getParameterValues(String name) {
        return parameterMap.get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return parameterMap;
    }

    public void setParameterMap(Map<String, String[]> parameterMap) {
        this.parameterMap = parameterMap;
    }


}

StringUtil.java

package until;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringUtil {
    private StringUtil() {
    }
    /**
     * 日志
     */
    private static Logger logger = Logger.getLogger(StringUtil.class);
    
    /**
     * @Description:    URLEncoder转为字符串
     * URLDecoder的decode方法会把加号+和%20都解码为空格,
     * URLEncoder.encode会把 加号+已被编码成%2B)
     * @param str 字符串
     * @param code 编码方式
     * @return java.lang.String
     * @throws
     */
    public static String urlCodeSpeciCharToStr(String str,String code) throws UnsupportedEncodingException {
        if(StringUtils.isNotEmpty(str)){
            str = str.replaceAll("\\+", "%20"); //空格+转为编码
            str =  URLDecoder.decode(str,code);
            HashMap<String,String> charMap = new HashMap<>();
            charMap.put("\\%20"," ");
            charMap.put("\\%2B","+");
            charMap.put("\\%2F","/");
            charMap.put("\\%3F","?");
            charMap.put("\\%25","%");
            charMap.put("\\%23","#");
            charMap.put("\\%26","&");
            charMap.put("\\%3D","=");
            for (Map.Entry<String, String> entry : charMap.entrySet()) {
                str = str.replaceAll(entry.getKey(),entry.getValue());
            }
            return str;
        }else{
            return str;
        }
    }

}

XSS拦截器

至此,我们的请求就包装完毕,下面就开始从我们包装后的请求中获取参数进行校验
XssInterceptor.java

package until;

import com.alibaba.fastjson.JSONObject;
import org.apache.log4j.Logger;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Set;

public class XssInterceptor implements HandlerInterceptor {
    /**
     * 日志对象
     */
    private Logger logger = Logger.getLogger(this.getClass());

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        if(httpServletRequest instanceof RequestWrapper || httpServletRequest instanceof MultipartHttpServletRequest){
            XssCheckUtils checkUtils = new XssCheckUtils();
            String pValue = null;
            String type="0";//校验模式 0:严格模式;1:html模式;2sql模式
            Map<String,String[]> pramMap = httpServletRequest.getParameterMap();
            if(pramMap != null && ! pramMap.isEmpty()){
                Set<String> keySet  = pramMap.keySet();
                for(String key : keySet){
                    pValue = (pramMap.get(key) != null && pramMap.get(key).length >0 ) ?  pramMap.get(key)[0] : "";
                    pValue = pValue.toUpperCase();
                    if (checkUtils.safeProtectFilter(pValue,type)) {//有非法字符
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put("code","1");
                        jsonObject.put("message","参数"+pValue+"含有非法字符");
                        returnJson(httpServletResponse,jsonObject.toJSONString());
                        return false;
                    }
                }
            }

        }
        return true;
    }
    /**
     * 返回客户端数据
     */
    private void returnJson(HttpServletResponse response, String result) throws Exception {
        PrintWriter writer = null;
        response.reset();//重置response
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        response.setStatus(707);//前台识别为参数非法
        response.setContentType("application/json;charset=UTF-8");
        try {
            writer = response.getWriter();
            writer.print(result);
        } catch (IOException e) {
        } finally {
            if (writer != null) {
                writer.close();
            }
        }

    }
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

正则校验类XssCheckUtils.java

package until;

import java.util.regex.Pattern;

public class XssCheckUtils {
    private static Pattern strictInvalidInputPattern = null;//严格校验
    private static Pattern looseInvalidInputPattern = null;//宽松校验 h5 相关元素
    static {
        try {
            strictInvalidInputPattern = Pattern.compile("<[\\s\\x00]*SCRIPT" + "|SELECT\\s" + "|ONFOCUS[\\s\\x00]*="
                    + "|STYLE[\\s\\x00]*=" + "|ONDBLCLICK[\\s\\x00]*=" + "|ONERROR[\\s\\x00]*=" + "|INSERT\\s"
                    + "|DELETE\\s" + "|ONKEYPRESS[\\s\\x00]*=" + "|LOCATION[\\s\\x00]*=" + "|UPDATE\\s" + "|DROP\\s"
                    + "|<!--|-->" + "|ONCLICK[\\s\\x00]*=ONDBLCLICK[\\s\\x00]*=" + "|ONHELP[\\s\\x00]*="
                    + "|ONKEYDOWN[\\s\\x00]*=" + "|ONKEYPRESS[\\s\\x00]*=" + "|ONKEYUP[\\s\\x00]*="
                    + "|ONMOUSEDOWN[\\s\\x00]*=" + "|ONMOUSEMOVE[\\s\\x00]*=" + "|ONMOUSEOUT[\\s\\x00]*="
                    + "|ONMOUSEOVER[\\s\\x00]*=" + "|ONMOUSEUP[\\s\\x00]*=" + "|ONABORT[\\s\\x00]*="
                    + "|ONERROR[\\s\\x00]*=" + "|ONLOAD[\\s\\x00]*=" + "|ONBLUR[\\s\\x00]*=" + "|ONCHANGE[\\s\\x00]*="
                    + "|ONCLICK[\\s\\x00]*=" + "|[\\s\\x00]*REFERER[\\s\\x00]*"
                    + "|[\\s\\x00]+ID[\\s\\x00]*=" + "|CLASS[\\s\\x00]*=" + "|CALL[\\s\\x00]*=" + "|EVAL[\\s\\x00]*="
                    + "|\\.[\\s\\x00]*LOAD[\\s\\x00]*\\(" + "|LOCATION[\\s\\x00]*\\.[\\s\\x00]*REPLACE[\\s\\x00]*\\("
                    + "|NAVIGATE[\\s\\x00]*\\(" + "|ONRESIZE[\\s\\x00]*=" + "|ONUMLOAD[\\s\\x00]*="
                    + "|DOCUMENT[\\s\\x00]*\\.\\[" + "|DOCUMENT[\\s\\x00]*\\.[\\s\\x00]*COOKIE"
                    + "|HANDLEEVENT[\\s\\x00]*\\(" + "|ROUTEEVENT[\\s\\x00]*\\(" + "|CAPTUREEVENTS\\("
                    + "|\\.[\\s\\x00]*WRITE[\\s\\x00]*\\(" + "|\\.[\\s\\x00]*WRITELN[\\s\\x00]*\\("
                    + "|\\.[\\s\\x00]*ACTION[\\s\\x00]*=" + "|HISTORY[\\s\\x00]*\\.[\\s\\x00]*BACK[\\s\\x00]*\\("
                    + "|HISTORY[\\s\\x00]*\\.[\\s\\x00]*GO[\\s\\x00]*\\(" + "|WINDOWS[\\s\\x00]*\\["
                    + "|DOCUMENT[\\s\\x00]*\\["
                    + "|STRING[\\s\\x00]*\\.[\\s\\x00]*FROMCHARCODE[\\s\\x00]*\\("
                    + "|ALERT[\\s\\x00]*\\(|CONFIRM[\\s\\x00]*\\(|FORM[\\s\\x00]*=" + "|FORMACTION[\\s\\x00]*="
                    + "|POSTER[\\s\\x00]*=" + "|ONINPUT[\\s\\x00]*=" + "|DIRNAME[\\s\\x00]*="
                    + "|BACKGROUNG[\\s\\x00]*=" + "|[\\s\\x00\\x27]+OR[\\s\\x00\\x27]+"
                    + "|[\\s\\x00\\x27]+LIKE[\\s\\x00\\x27]+" + "|[\\s\\x00\\x27]+\\|\\[\\s\\x00\\x27]+"
                    + "|DECLARE[\\s\\x00]+[\\w\\W]*[\\s\\x00]+VARCHAR"
                    + "|DECLARE[\\s\\x00]+[\\w\\W]*[\\s\\x00]+NVARCHAR"
                    + "|DECLARE[\\s\\x00]+[\\w\\W]*[\\s\\x00]+CURSOR"
                    + "|SET[\\s\\x00]+[\\w\\W]*[\\s\\x00]*=[\\s\\x00]*CAST"
                    + "|PROMPT[\\s\\x00]*\\(\\\"[\\s\\x00]*>[\\s\\x00]*<" + "|<FRAME|<IFRAME|<FRAMESET|<NOFRAME"
                    + "|<PLAINTEXT|<A|<LINK|<MAP|<BGSOUND" + "|<FORM|<INPUT|<SELECT|<OPTION|<TEXTAREA"
                    + "|<IMG|<APPLET|<OBJECT|<EMBED|<NOSCRIPT|<STYLE" + "|ALERT[\\s\\x00]*\\(" + "|OPEN[\\s\\x00]*\\("
                    + "|[\\s\\x00]*REFERER[\\s\\x00]*"
                    + "|[\\s\\x00]*PROMPT[\\s\\x00]*" + "|%[\\d00]+" + "|SHOWMODALDIALOG[\\s\\x00]*\\("
                    + "|SHOWMODELESSDIALOG[\\s\\x00]*\\(");

            looseInvalidInputPattern = Pattern.compile("SELECT\\s"
                    + "|INSERT\\s"
                    + "|DELETE\\s" + "|UPDATE\\s" + "|DROP\\s"
                    + "|HISTORY[\\s\\x00]*\\.[\\s\\x00]*GO[\\s\\x00]*\\(" + "|WINDOWS[\\s\\x00]*\\["
                    + "|DOCUMENT[\\s\\x00]*\\["
                    + "|STRING[\\s\\x00]*\\.[\\s\\x00]*FROMCHARCODE[\\s\\x00]*\\("
                    + "|DECLARE[\\s\\x00]+[\\w\\W]*[\\s\\x00]+VARCHAR"
                    + "|DECLARE[\\s\\x00]+[\\w\\W]*[\\s\\x00]+NVARCHAR"
                    + "|DECLARE[\\s\\x00]+[\\w\\W]*[\\s\\x00]+CURSOR");
        } catch (Exception err) {
            strictInvalidInputPattern = null;
            looseInvalidInputPattern = null;
        }
    }

    public XssCheckUtils() {

    }

    /**
     * 正则校验值
     * @param parStr 值串
     * @param type 0(默认):严格模式;1:宽松模式
     * @return
     */
    public boolean safeProtectFilter(String parStr,String type) {
        if (parStr != null) {
            parStr = parStr.toUpperCase();
            if(type != null && "1".equals(type)){//宽松校验
                if (looseInvalidInputPattern.matcher(parStr).find()) {
                    return true;
                }
            }else{//严格校验
                if (strictInvalidInputPattern.matcher(parStr).find()) {
                    return true;
                }
            }
        }
        return false;
    }
}

配置拦截器

springmvc.xml

<mvc:interceptors>
  <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/xx/xx" /> <!--白名单接口-->
        <bean class="until.XssInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

原文链接:https://blog.csdn.net/weixin_38861291/article/details/109777166



所属网站分类: 技术文章 > 博客

作者:java之恋

链接:http://www.javaheidong.com/blog/article/879/32c15fcbba1d653779e6/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

9 0
收藏该文
已收藏

评论内容:(最多支持255个字符)