request请求的body中的参数(json对象)只能取出一次,参数丢失问题的解决方式(防sql注入过滤器的应用)

在项目即将上线的渗透测试报告中检测出了sql注入的问题,关于这个问题的解决方案,最初的思路是写一个全局的过滤器,对所有请求的参数进行过滤拦截,如果存在和sql注入相关的特殊字符则拦截掉,具体细节展开以下讨论!

(当然要提供一个白名单,白名单里的请求不给予过滤)

首先提供以下白名单code.properties
# 鉴权码
# IDAM鉴权(多个以逗号分隔)
authcode=32j42i3
# 防sql注入请求白名单
sqlverify=/ryjh/mappingGroup/updateInfo,\
  /author/Logon/loginConfigCheck,\
  /author/Logon/login,\
  /author/SAuUser/resetPwd,\
  /author/SAuUser/addUser,\
  /swagger-resources/configuration/ui,\
  /swagger-resources,\
  /doc.html
第一版的过滤器如下
/**
 * @author FanJiangFeng
 * @version 1.0.0
 * @ClassName SqlFilter.java
 * @Description 防止Sql注入过滤器,校验参数
 * @createTime 2021年01月05日 17:08:00
 */
@Component
@WebFilter(value = "/")
public class SqlFilter implements Filter {

    //Sql注入配置文件白名单绝对路径
    @Value("${auth.authCodeUrl}")
    private String url;


    private boolean verify(String uri) throws IOException {
        Properties properties=new Properties();
        InputStream inputStream=new FileInputStream(new File(url));
        properties.load(inputStream);
        Map<String,String> codeMap=(Map)properties;
        String whiteDoc=codeMap.get("sqlverify");
        String[] strings = whiteDoc.split(",");
        boolean over=false;
        for(String s:strings){
            if(s.equals(uri)){
                over=true;
                break;
            }
        }
        return over;
    }

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

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request=(HttpServletRequest)servletRequest;
        String contentType = request.getContentType();
        String requestURI = request.getRequestURI();
        boolean verify = verify(requestURI);
        if(verify){
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }   
            //application/x-www-form-urlencoded
            Map<String, String[]> parameterMap = request.getParameterMap();
            for(Map.Entry<String,String[]> entry:parameterMap.entrySet()){
//                String strings = entry.getKey();
                //校验参数名是否合法
//            boolean isTrue = verifySql(strings);
//            if(!isTrue){
//                return;
//            }
                //校验参数值是否合法
                String[] value = entry.getValue();
                for(String s:value){
                    //校验参数值是否合法
                    boolean b = verifySql(s);
                    if(!b){
                        return;
                    }
                }
        }
        
            filterChain.doFilter(servletRequest,servletResponse);
        	return;
    }

    @Override
    public void destroy() {

    }

    /**
     * 校验参数非法字符
     */
    public boolean verifySql(String parameter){
        if(parameter.contains("'")){ //' 单引号
            return false;
        }else if(parameter.contains("\"")){ //" 双引号
            return false;
        }else if(parameter.contains("\\'")){//' 反斜杠单引号
            return false;
        }else if(parameter.contains("\\\"")){//" 反斜杠双引号
            return false;
        }else if(parameter.contains("(")||parameter.contains(")")||parameter.contains(";")){//括号和分号
            return false;
        }else if(parameter.contains("--")||parameter.contains("+")){//双减号 加号
            return false;
        }else if(parameter.toLowerCase().contains("select")||parameter.toLowerCase().contains("update")
        ||parameter.toLowerCase().contains("delete")||parameter.toLowerCase().contains("drop")
        ||parameter.toLowerCase().contains("updatexml")||parameter.toLowerCase().contains("concat")){
            return false;
        }
        return true;

    }
}

第一个版本的不足:

它只能解析content-type为application/x-www-form-urlencoded的请求携带的参数

由Map<String, String[]> parameterMap = request.getParameterMap()的方式进行获取

但是它解析不了content-type类型为application/json格式的参数 ,上面那种方式已经获取不到了,所以要重新改版。

我是如何跳坑的?

刚开始我新加了一个方法,传入request对象,然后从request对象中拿到json字符串格式的参数,通过对字符串进行转换校验等处理,然后达到目的效果,但是我发现,处理之后,虽然过滤器放开了这个请求,当请求来到controller时,参数消失了?

这是因为,request请求中的body参数只可以拿出来一次,拿出来就没有了!

解决方案

需要一个类继承HttpServletRequestWrapper,该类继承了ServletRequestWrapper并实现了HttpServletRequest,

因此它可作为request在FilterChain中传递。

该类需要重写getReader和getInputStream两个方法,并在返回时将读出的body数据重新写入。

参考文章:https://my.oschina.net/u/4335633/blog/4252883

新建BodyReaderRequestWrapper类
public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {
    private final String body;

    public String getBody() {
        return body;
    }

    /**
     * 取出请求体body中的参数(创建对象时执行)
     * @param request
     */
    public BodyReaderRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder sb = new StringBuilder();
        InputStream ins = request.getInputStream();
        BufferedReader isr = null;
        try{
            if(ins != null){
                isr = new BufferedReader(new InputStreamReader(ins));
                char[] charBuffer = new char[128];
                int readCount = 0;
                while((readCount = isr.read(charBuffer)) != -1){
                    sb.append(charBuffer,0,readCount);
                }
            }else{
                sb.append("");
            }
        }catch (IOException e){
            throw e;
        }finally {
            if(isr != null) {
                isr.close();
            }
        }

        sb.toString();
        body = sb.toString();
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletIns = 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 byteArrayIns.read();
            }
        };
        return  servletIns;
    }
}
filter过滤器更改

在dofilter方法中创建BodyReaderRequestWrapper对象,并继续传递。

		BodyReaderRequestWrapper wrapper=null;
        if("application/json".equals(contentType)){
            wrapper=new BodyReaderRequestWrapper(request);
            ......
                
        if(wrapper==null){
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            filterChain.doFilter(wrapper,servletResponse);
        }

既然可以获取到json对象的字符串信息了,那么开始写对json的校验过程

对json格式参数递归解析

讨论:json格式的参数种类很多,比如

{
"id":"test",
"name":"test"
}
[
    {
        "id":"test",
        "name":"test"
	}
    {
        "id":"test",
        "name":"test"
	}
]
{
"id":"test",
"name":[
            {
                "id":"test",
                "name":"test"
            }
            {
                "id":"test",
                "name":"test"
            }
		]
}

以及更多,所以这里采用递归解析的方式

过滤器的最终版本
@Component
@WebFilter(value = "/")
public class SqlFilter implements Filter {

    //Sql注入配置文件白名单绝对路径
    @Value("${auth.authCodeUrl}")
    private String url;


    private boolean verify(String uri) throws IOException {
        Properties properties=new Properties();
        InputStream inputStream=new FileInputStream(new File(url));
        properties.load(inputStream);
        Map<String,String> codeMap=(Map)properties;
        String whiteDoc=codeMap.get("sqlverify");
        String[] strings = whiteDoc.split(",");
        boolean over=false;
        for(String s:strings){
            if(s.equals(uri)){
                over=true;
                break;
            }
        }
        return over;
    }

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

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request=(HttpServletRequest)servletRequest;
        String contentType = request.getContentType();
        String requestURI = request.getRequestURI();
        boolean verify = verify(requestURI);
        if(verify){
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }
        BodyReaderRequestWrapper wrapper=null;
        if("application/json".equals(contentType)){
            wrapper=new BodyReaderRequestWrapper(request);
            String requestPostStr = wrapper.getBody();
            if (requestPostStr.startsWith("{")) {
                //解析json对象
                boolean b = resolveJSONObjectObj(requestPostStr);
                if(!b)return;

            }else if (requestPostStr.startsWith("[")) {
                //把数据转换成json数组
                JSONArray jsonArray = JSONArray.parseArray(requestPostStr);
                jsonArray.forEach(json -> {
                    //解析json对象
                    boolean b = resolveJSONObjectObj(json.toString());
                    if(!b)return;
                });
            }

        }else{
            //application/x-www-form-urlencoded
            Map<String, String[]> parameterMap = request.getParameterMap();
            for(Map.Entry<String,String[]> entry:parameterMap.entrySet()){
//                String strings = entry.getKey();
                //校验参数名是否合法
//            boolean isTrue = verifySql(strings);
//            if(!isTrue){
//                return;
//            }
                //校验参数值是否合法
                String[] value = entry.getValue();
                for(String s:value){
                    //校验参数值是否合法
                    boolean b = verifySql(s);
                    if(!b){
                        return;
                    }
                }
            }
        }
        if(wrapper==null){
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            filterChain.doFilter(wrapper,servletResponse);

        }
        return;
    }

    /**
     * 对JSONObject对象进行递归参数解析
     *
     * @param requestPostStr
     * @return
     */
    private boolean resolveJSONObjectObj(String requestPostStr) {
        boolean isover=true;
        // 创建需要处理的json对象
        JSONObject jsonObject = JSONObject.parseObject(requestPostStr);
        // 获取所有的参数key
        Set<String> keys = jsonObject.keySet();
        if (keys.size() > 0) {
            for (String key : keys) {
                //获取参数名称
                String value = null;
                if (jsonObject.get(key) != null) {
                    value = String.valueOf(jsonObject.get(key));
                    //当value为数组时
                    if(value.startsWith("[")){
                        //把数据转换成json数组
                        JSONArray jsonArray = JSONArray.parseArray(value);
                        for(int i=0;i<jsonArray.size();i++){
                            //解析json对象
                            boolean b = resolveJSONObjectObj(jsonArray.get(i).toString());
                            if(!b){
                                isover=false;
                                break;
                            }
                        }
                    }else if(value.startsWith("{")){
                        boolean b = resolveJSONObjectObj(value);
                        if(!b){
                            isover=false;
                            break;
                        }
                    }else{
                        //校验参数值是否合法
                        boolean b = verifySql(value);
                        if(!b){
                            isover=false;
                            break;
                        }
                    }
                }
            }
        }
        return isover;
    }


    @Override
    public void destroy() {

    }

    /**
     * 校验参数非法字符
     */
    public boolean verifySql(String parameter){
        if(parameter.contains("'")){ //' 单引号
            return false;
        }else if(parameter.contains("\"")){ //" 双引号
            return false;
        }else if(parameter.contains("\\'")){//' 反斜杠单引号
            return false;
        }else if(parameter.contains("\\\"")){//" 反斜杠双引号
            return false;
        }else if(parameter.contains("(")||parameter.contains(")")||parameter.contains(";")){//括号和分号
            return false;
        }else if(parameter.contains("--")||parameter.contains("+")){//双减号 加号
            return false;
        }else if(parameter.toLowerCase().contains("select")||parameter.toLowerCase().contains("update")
        ||parameter.toLowerCase().contains("delete")||parameter.toLowerCase().contains("drop")
        ||parameter.toLowerCase().contains("updatexml")||parameter.toLowerCase().contains("concat")){
            return false;
        }
        return true;

    }
}

这样,什么格式的json参数都会解析到!如果有任何问题可以联系本人,可以共同探讨!

posted @ 2021-01-19 12:23  你樊不樊  阅读(1420)  评论(0编辑  收藏  举报