关于防止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) {

                }
            };
        }

 

posted on 2022-04-29 14:40  永不宕机  阅读(865)  评论(0编辑  收藏  举报

导航