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參數都會解析到!如果有任何問題可以聯系本人,可以共同探討!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM