• 关于防止xss攻击过程中遇到的坑


    xss攻击的解决过程

    1.添加一个拦截器,在拦截器中对request中的body体进行读取

    2.对body体中的特殊字符进行转码或者进行过滤

    3.将处理过的body重新塞回request

    具体操作可参考:https://www.cnblogs.com/0201zcr/p/13143165.html

    在实际项目中,我们的接口支持json中携带注释,根据以上操作添加拦截器之后,调用携带注释的请求体一直报错;

    入参如下:

    过滤器中代码如下:

    package com.insgeek.boot.web.filter;
    
    import cn.hutool.core.text.CharSequenceUtil;
    import cn.hutool.http.Header;
    import cn.hutool.http.HtmlUtil;
    import com.fasterxml.jackson.core.type.TypeReference;
    import com.google.common.collect.Maps;
    import com.insgeek.boot.commons.json.JacksonUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ReadListener;
    import javax.servlet.ServletException;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    import java.nio.charset.StandardCharsets;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * 防止xss攻击
     * @author holley
     */
    @Slf4j
    @Component
    public class XssFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            filterChain.doFilter(new XssHttpServletRequestWrapper(request),response);
        }
    
        public static class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
            public XssHttpServletRequestWrapper(HttpServletRequest request) {
                super(request);
            }
    
            /**
             * 重写getParameter方法,用HtmlUtil转义后再返回
             */
            @Override
            public String getParameter(String name) {
                String value= super.getParameter(name);
                if(!CharSequenceUtil.hasEmpty(value)){
                    value= HtmlUtil.filter(value);
                }
                return value;
            }
    
            /**
             * 重写getParameterValues方法,
             * 遍历每一个值,用HtmlUtil转义后再返回
             */
            @Override
            public String[] getParameterValues(String name) {
                String[] values= super.getParameterValues(name);
                if(values!=null){
                    for (int i=0;i<values.length;i++){
                        String value=values[i];
                        if(!CharSequenceUtil.hasEmpty(value)){
                            value=HtmlUtil.filter(value);
                        }
                        values[i]=value;
                    }
                }
                return values;
            }
    
            /**
             * 重写getParameterMap方法,
             * 拿到所有的k-v键值对,用LinkedHashMap接收,
             * key不变,value用HtmlUtil转义后再返回
             */
            @Override
            public Map<String, String[]> getParameterMap() {
                Map<String, String[]> parameters = super.getParameterMap();
                LinkedHashMap<String, String[]> map= Maps.newLinkedHashMap();
                if(parameters!=null){
                    for (String key:parameters.keySet()){
                        String[] values=parameters.get(key);
                        for (int i = 0; i < values.length; i++) {
                            String value = values[i];
                            if (!CharSequenceUtil.hasEmpty(value)) {
                                value = HtmlUtil.filter(value);
                            }
                            values[i] = value;
                        }
                        map.put(key,values);
                    }
                }
                return map;
            }
    
            /**
             * 重写getHeader方法,用HtmlUtil转义后再返回
             */
            @Override
            public String getHeader(String name) {
                String value= super.getHeader(name);
                if (!CharSequenceUtil.hasEmpty(value)) {
                    value = HtmlUtil.filter(value);
                }
                return value;
            }
    
            @Override
            public ServletInputStream getInputStream() throws IOException {
                /**
                 * 拿到数据流,通过StringBuffer拼接,
                 * 读取到line上,用StringBuffer是因为会有多个线程同时请求,要保证线程的安全
                 */
                InputStream in= super.getInputStream();
                InputStreamReader reader=new InputStreamReader(in);
                BufferedReader buffer=new BufferedReader(reader);
                StringBuffer body = new StringBuffer();
                // 注意问题出在这里!!!!
                String line=buffer.readLine();
                while(line!=null){
                    body.append(line);
                    line=buffer.readLine();
                }
                buffer.close();
                reader.close();
                in.close();
                /**
                 * 将拿到的map,转移后存到另一个map中
                 */
    //            Map<String,Object> map= JacksonUtils.readValue(body.toString(), new TypeReference<Map<String, Object>>() {});
    //            Map<String,Object> result=new LinkedHashMap<>();
    //            for(String key:map.keySet()){
    //                Object val=map.get(key);
    //                if(val instanceof String){
    //                    if(!CharSequenceUtil.hasEmpty(val.toString())){
    //                        result.put(key,HtmlUtil.filter(val.toString()));
    //                    }
    //                }else {
    //                    result.put(key,val);
    //                }
    //            }
    //            String json = JacksonUtils.writeAsString(result);
                InputStream bain = new ByteArrayInputStream(body.toString().getBytes());
                //匿名内部类,只需要重写read方法,把转义后的值,创建成ServletInputStream对象
                return new ServletInputStream() {
                    @Override
                    public int read() throws IOException {
                        return bain.read();
                    }
    
                    @Override
                    public boolean isFinished() {
                        return false;
                    }
    
                    @Override
                    public boolean isReady() {
                        return false;
                    }
    
                    @Override
                    public void setReadListener(ReadListener readListener) {
    
                    }
                };
            }
        }
    }

    在过滤器中只是将流转成字符串,然后再转回流,中间没有其它操作,结果在后面的AbstractJackson2HttpMessageConverter类中解析json进行实体映射时,抛出异常

    JSON parse error: Unexpected end-of-input: ... PushbackInputStream ... JsonEOFException

    最后排查发现:在使用bufferReader读取流时,使用readline方法会忽略换行符,导致最后生成的字符串变成了一行,即:{    "action_type":1,    // "entity_key": "qp_quote_info",    "entity_key": "ig_claim_list",    // "entity_key": "ig_insurance_apply",    // "entity_key": "qp_quote",    // "entity_key": "holley",    "data_id": "1500000",    // "assigner":1997,    "variables": {        // "name2":"aaa",        // "name":"Holley1",        // "name4":"Holley4"        // "channel_id":"1234" // 渠道id    }}。也就是说:从第一个// 之后,所有的数据都读去成注释了,导致json转换失败

    解决方案:使用ByteArrayOutputStream读取数据进行转换,保留换行符

    @Override
            public ServletInputStream getInputStream() throws IOException {
                InputStream in= super.getInputStream();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int len = -1;
                byte[] buffer = new byte[1024];//1kb
                while ((len = in.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }
                in.close();
                String json = new String(baos.toByteArray());
    
                InputStream bain = new ByteArrayInputStream(XssUtil.cleanXss(json).getBytes());
                //匿名内部类,只需要重写read方法,把转义后的值,创建成ServletInputStream对象
                return new ServletInputStream() {
                    @Override
                    public int read() throws IOException {
                        return bain.read();
                    }
    
                    @Override
                    public boolean isFinished() {
                        return false;
                    }
    
                    @Override
                    public boolean isReady() {
                        return false;
                    }
    
                    @Override
                    public void setReadListener(ReadListener readListener) {
    
                    }
                };
            }
  • 相关阅读:
    Mac下安装svn服务器
    php 当前日期加一天和指定日期加一天
    MariaDB与MySQL
    JS中判断null、undefined与NaN的方法
    PHP保留两位小数的几种方法
    jquery的cookie插件
    mysql(5.7以上)查询报错:ORDER BY clause is not in GROUP BY..this is incompatible with sql_mode=only_full_group_by
    MySQL数据的导出和导入
    qrCode二维码字符串长度太多压缩的问题
    解决 img 标签上下出现的间隙
  • 原文地址:https://www.cnblogs.com/zhlblogs/p/16206617.html
Copyright © 2020-2023  润新知