越权漏洞的处理方案

1.越权漏洞

  使用postman模拟请求时,对于一些请求参数做修改从而获取到不是登录用户权限所能获取得到的数据。例如:总机构可以查询到所有机构的用户信息,分机构只能查询到所在机构的用户信息,但是通过使用分机构登录的sessionid与接口修改其中机构的参数,改为总机构的就可以获取到所有机构的用户信息。

2.解决方案

  使用签名防止请求报文被篡改。前台对请求参数的key排序(对于没有请求参数的可以加一个请求时间戳作为参数)然后使用md5加密,后台验证签名,防止报文被篡改。

2.1.前台vue签名功能代码

import md5 from 'js-md5'

export default calss signMd5Utils{
  static sortAsc(jsonObj){
    delete jsonObj.sign
    let arr
= new Array() let num = 0   // 把json数据的key取出排序 for(let i jsonObj){
      arr[num]
= i    num++   }   let sortArr = arr.sort()   let sortObj = {}   for(let i in sortArr){   sortObj[sortArr[i]] = jsonObj[sortArr[i]]   }   return sortObj } // 使用MD5进行加密 static getSign(reqParams){   let reqBody = this.sortAsc(reqParams)   return md5(JSON.stringify(reqBody)).toUpperCase() } }

2.2.在调用接口的时候对参数添加签名

import signMd5Utils from '@/src/utils/signutils'
getVales(){
  let params = {pageNo: this.pageInfo.pageNo, pageSize: this.pageInfo.pageSize}
  let signPars = object.assign(params, {sign : signMd5Utils.getSign(params)})
  this.$post('/api/queryList', signPars).then( res => {
      console.log("响应数据")
  })
}

2.3.后台校验签名

检验签名的Filter

package com.example.springbootdemo.filter;

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.SortedMap;

@Component
@WebFilter(filterName = "signFilter", urlPatterns = "/*")
public class SignFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String contentType = request.getContentType();
        HttpServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            if (contentType != null && contentType.contains("multipart/form-data")) {
                requestWrapper = (HttpServletRequest) request;
            } else {
                requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
            }
        }else{
            requestWrapper = (HttpServletRequest) request;
        }
        SortedMap<String, Object> allParams = RequestParamsUtils.sortAllParams(requestWrapper);
        boolean signRlt = RequestParamsUtils.verifySign(allParams);
        System.out.println(String.format("签名认证:%s,请求地址:%s,请求参数:%s", signRlt ? "通过" : "不通过",
                requestWrapper.getRequestURI(), allParams));
        if(!signRlt){
            final JSONObject expResp = new JSONObject();
            expResp.put("code", "403");
            expResp.put("msg", "签名认证不通过");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            final PrintWriter writer = response.getWriter();
            writer.append(expResp.toJSONString());
            return;
        }
        chain.doFilter(requestWrapper, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

对请求参数处理的工具类

package com.example.springbootdemo.filter;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
import org.springframework.util.DigestUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

public class RequestParamsUtils {

    /**
     * 验证签名(鉴权)
     * @param sortAllParams
     * @return
     */
    public static boolean verifySign(SortedMap<String, Object> sortAllParams){
       String sign = (String) sortAllParams.get("sign");
       sortAllParams.remove("sign");
       final String paramsJson = JSONObject.toJSONString(sortAllParams, SerializerFeature.PrettyFormat,
               SerializerFeature.WriteMapNullValue);
        sortAllParams.put("sign", sign);
        // 对参数排序后获取签名,与传入的参数签名进行比对
        String newSign = DigestUtils.md5DigestAsHex(paramsJson.getBytes()).toUpperCase();
        return StringUtils.isNotBlank(newSign) && StringUtils.equals(sign, newSign);
    }

    /**
     * 将获取到的参数进行整理排序
     * @param request
     * @return
     */
    public static SortedMap<String, Object> sortAllParams(HttpServletRequest request){
        TreeMap<String, Object> sortAllParams = new TreeMap<>();
        sortAllParams.putAll(getBodyParams(request));
        sortAllParams.putAll(getParameterMap(request));
        return sortAllParams;
    }

    /**
     * 获取RequestBody中的参数
     * @param request
     * @return
     */
    public static Map<String, Object> getBodyParams(HttpServletRequest request) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        BufferedInputStream br = null;
        try {
            // 这里不能使用getInputStream读取,使用了后面再次读取会读取不到
            /*br = new BufferedInputStream(request.getInputStream());
            for(int c=0; (c=br.read())!=-1;){
                bos.write(c);
            }
            String ok = bos.toString();
            return JSONObject.parseObject(ok, Map.class);*/
            // 判断一下,排除 multipart/form-data数据时没有转为ContentCachingRequestWrapper
            if (request instanceof ContentCachingRequestWrapper) {
                // 使用缓存流读取数据
                byte[] contentData = ((ContentCachingRequestWrapper) request).getContentAsByteArray();
                // IOUtils.toString(contentData, "utf-8");
                return JSONObject.parseObject(new String(contentData, "utf-8"), Map.class);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            // BufferedInputStream这里关闭流时,chain.doFilter()那里是会有异常:java.io.IOException: Stream closed
          /*  if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                   e.printStackTrace();
                }
            }
        }
        return new HashMap<>();
    }

    /**
     * 获取Request的请求参数
     * @param request
     * @return
     */
    public static Map<String, Object> getParameterMap(HttpServletRequest request) {
        try {
            Map<String, String[]> parameterMap = request.getParameterMap();
            Map<String, Object> paramsMap = new HashMap();
            // 默认参数
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                String value = "";
                String key = entry.getKey();
                Object valueObj = entry.getValue();

                if (valueObj == null) {
                    value = "";
                } else if (!(valueObj instanceof String[])) {
                    value = valueObj.toString();
                } else {
                    String[] values = (String[])valueObj;
                    for (String temp : values) {
                        value = temp + ",";
                    }
                    value = value.substring(0, value.length() - 1);
                }
                paramsMap.put(key, value);
            }
            return paramsMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new HashMap<>();
    }

}

3.测试验证

当签名验证通过时,功能正常。当修改name的值,签名认证不通过。加密方式前后台约定好,不知道加密方式篡改请求参数无法获取到数据。

 

 

posted @ 2023-07-13 15:38  话祥  阅读(2046)  评论(0编辑  收藏  举报