web系统设计缺陷漏洞:防止修改响应包,造成逻辑漏洞

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/songyanfei1205/article/details/80885658
背景:
   使用SSM做的web系统,在进行渗透测试的时候暴露出漏洞:设计缺陷:修改响应包,扰乱系统正常逻辑
设计缺陷/逻辑漏洞包括:
   1 修改数值;
   2 验证码爆破;
   3 修改相应包;
   4 修改密码;
   5 服务端无有效性校验;
整改方案:(此方案还是有缺陷)
   1 首先保证所有功能在服务器端做有效性校验。例如,应该填写数字的地方不允许保存别的字符等。
   2 在所有请求发送之前,统一追加一个随机码;此随机码需要经过一定的算法及排序生成。随机码的作用是确保返回值是由后台发送的。
     此处楼主将随机码分成长短不同的4段,使用4个自定义请求头携带发送。
   3 后台在返回时,使用过滤器进行拦截所有返回请求,对所有返回值进行加密处理,类似于简单的数字签名
   3 在请求返回之前,将获取到的数据进行预处理。好在jquery已经具备这个方法: dataFilter.
   具体思路是
     前台:
     基于系统大部分功能都是jquery异步请求,在所有页面引入的公共js文件中加入方法,
	 即$.ajaxSetup({
		beforeSend: function(xhr) {//x-hhkj是自定义的header头名称
		   xhr.setRequestHeader("x-hhkj", '随机码');
		},
		headers: {'x-hhkj':'随机码'},//由于未知原因,有些时候beforeSend这个方法不会被调用,故增加方法header。
		dataFilter:function(data,type){//返回值data为原始数据  ,type是数据类型
			//处理data
			return data;
		}
	 });
	后台:
	 使用代理将返回值获取到,使用过滤器对返回值进行处理,然后重写返回值到前台。
	 
注:
 此方案的问题症结是随机码可能被破解。
 所有涉及加密解密的js文件全部进行混淆加密。
 楼主定义的随机码位19位长度,字母加数字的格式注:
 此方案的问题症结是随机码可能被破解。
 所有涉及加密解密的js文件全部进行混淆加密。
 楼主定义的随机码位19位长度,字母加数字的格式
可使用Fiddler工具或者Burb suite工具进行测试。如果需要这两个工具,可联系楼主
楼主整改的案例demo:需要将ResponseFilter.java在web.xml中配置或者使用注解注册
楼主整改的案例demo:需要将ResponseFilter.java在web.xml中配置或者使用注解注册
前台引入的js:

var random_num = '';//定义一个随机数
$.ajaxSetup({
	  beforeSend: function(request) {
		  random_num = createRandom();
		  var st = random_num.split(',');
	      request.setRequestHeader("x-agent-with", st[0]);
		  request.setRequestHeader("hhkj-Language", st[1]);
		  request.setRequestHeader("fildpath", st[2]);
		  request.setRequestHeader("ranne-x", st[3]);
		  random_num = st[2] + st[1] + st[0] + st[3];
	  },
	  headers: createHeaderRandom(),
      dataFilter:function(data,type){
          	if(!data){
          		return null;
          	}
			//此处的变量值是为了识别后台返回的数据是否加密。目前楼主的只有返回值是json串的才进行加密处理
          	var siencrpty = '0e5a0c2cca384a1b';
          	if(data.indexOf(siencrpty) == -1){//如果没有加密,则直接返回值,不进行下一步的处理
          		return data;
          	}
			//此处是为了防止有个别返回值是页面或者脚本漏掉的,也需要放行
          	if(type == 'html' 
    			|| type == 'script'){
    			return data;
    		}
			//开始进行鉴别
          	var res = data;
          	res = res.replace(siencrpty,'');//先将验证返回值是否加密的表示移除掉
          	var arr = res.split("|"); //后台返回的值得格式是:摘要|数据 。所以需要进行拆分  	
          	var orginData = arr[1];//原始数据
          	var priKey = $.trim(arr[0]);//摘要
    		if(random_num){//首先判断随机码是否存在,如果存在,则处理
				//先将随机码反向输出
    			random_num = random_num.split('').reverse().join('');
    			//截取摘要的后8位字符
    			var r1 = priKey.match(/.*(.{8})/)[1] ;
    			//截取摘要的前9位字符
    			var r2 = priKey.substring(0,9);
    			/*
				*如果摘要的后8位字符是随机码的开头,摘要的前9位字符是随机码的结尾,则证明数据未遭到篡改
				*原因是:后台为了加大随机码校验的难度,将传送的19位随机码经过处理,移除掉2位,
				*再把剩下的17位拆分成两半部分分别追加到后台生成的摘要首尾
				*/
    		    if(random_num.indexOf(r1) == 0 && random_num.indexOf(r2) == 10 ){
    		    	priKey=priKey.substring(9,priKey.length - 8);
    				random_num= '';
    			}else{
    				alert("数据异常,请重新登录!");
    				return null;
    			}
    		
          	}
			//将原始数据使用base64加密
          	var base64Data = Base64Copy.encode(orginData);
			//将base64加密的原始数据使用md5加密(32位)
          	var MD5Data = hex_md5(base64Data);
          	//截取将base64加密的原始数据的前半部分
          	base64Data = base64Data.substring(0,base64Data.length/2);
          	//截取将md5加密的数据的后半部分
          	MD5Data = MD5Data.substring(MD5Data.length/2,MD5Data.length);
          	//前台生成摘要
          	var remark = $.trim(base64Data)+$.trim(MD5Data);
          	//将前台生成的摘要与后台生成的摘要作比较,如果不一样,则遭到篡改
          	if(priKey.indexOf(remark) == -1){//此处可自由修改判断条件
    			alert("数据异常,请重新登录!");
    			loginOut();
      	    	return null;
          	}   
      	    return orginData;
          },
    	  cache:false
    });

 //产生随机数函数  
function createRandom(){
	var str="zxc1vbn2mlkjhg3fdsa4qwert5yuiop8QWERTY6UIOPAS9DFGHJK7LZXCVBNM0";
    var rnd="";
    for(var i=0;i<19;i++)
        rnd+=str.charAt(Math.floor(Math.random()*10));
	 var s1 = rnd.substring(0,6);
	  var s2 = rnd.substring(6,10);
	  var s3 = rnd.substring(10,17);
	  var s4 = rnd.substring(17);
    return s1 + ','+s2+','+s3+','+s4;
}
//产生随机数函数
function createHeaderRandom(){
	var str="zxc1vbn2mlkjhg3fdsa4qwert5yuiop8QWERTY6UIOPAS9DFGHJK7LZXCVBNM0";
    var rnd="";
    for(var i=0;i<19;i++)
        rnd+=str.charAt(Math.floor(Math.random()*10));
	 var s1 = rnd.substring(0,6);
	  var s2 = rnd.substring(6,10);
	  var s3 = rnd.substring(10,17);
	  var s4 = rnd.substring(17);
	  random_num = s3 + s2 + s1 + s4;
    return {
		"x-agent-with":s1,
		"hhkj-Language":s2,
		"fildpath":s3,
		"ranne-x":s4
	};
}
package com.system.filter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

import com.system.EncryptDataUtils;



/**
 * 返回值输出过滤器,这里用来加密返回值
 * 
 * @Title: ResponseFilter
 * @Description:
 * @author kokJuis
 * @date 上午9:52:42
 */
public class ResponseFilter implements Filter
{

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws IOException, ServletException
	{
		HttpServletRequest httprequest = (HttpServletRequest) request;
		HttpServletResponse httpresponse=(HttpServletResponse)response;
		String url = httprequest.getRequestURL().toString();
		if(check(url)){
			filterChain.doFilter(request, response);
			return;
		}
		ResponseWrapper wrapperResponse = new ResponseWrapper((HttpServletResponse)response);//转换成代理类
		// 这里只拦截返回,直接让请求过去,如果在请求前有处理,可以在这里处理 
		
		
		filterChain.doFilter(request, wrapperResponse);
		if("application/json;charset=UTF-8".equals(wrapperResponse.getContentType()) || 
				"application/json;charset=utf-8".equals(wrapperResponse.getContentType())){
			String headerName1 = httprequest.getHeader("x-agent-with");
			String headerName2 = httprequest.getHeader("hhkj-Language");
			String headerName3 = httprequest.getHeader("fildpath");
			String headerName4 = httprequest.getHeader("ranne-x");
			String headerName = headerName3 + headerName2 + headerName1 + headerName4;
			//针对某些特殊的链接 前台无法追加header的 可追加到链接之后  此处根据各自情况而定
			if(headerName == null || "".equals(headerName) || "null".equals(headerName) || headerName.length() < 19){
				String headerName11 = httprequest.getHeader("x-agent-with");
				String headerName21 = httprequest.getHeader("hhkj-Language");
				String headerName31 = httprequest.getHeader("fildpath");
				String headerName41 = httprequest.getHeader("ranne-x");
				headerName = headerName31 + headerName21 + headerName11 + headerName41;
			}
			if(headerName == null || "".equals(headerName) || "null".equals(headerName) || headerName.length() < 19){
				headerName = "";
			}
			byte[] content = wrapperResponse.getContent();//获取返回值
			
			
				String str = new String(content,"utf-8");
				
				String res = "";
				
				if(url.indexOf("message/page/0") != -1 
						|| url.indexOf("message/page/1") != -1 
						|| url.indexOf("common/todotasks") != -1
						|| url.indexOf("common/getMessage") != -1){//此处需要根据各自业务逻辑进行不加密处理防范 TODO
					res = str;
				}else{
					//随机码倒叙
					res = EncryptDataUtils.Encrypt(str,new StringBuffer(headerName).reverse().toString());
				}
				//注意:防止自动截取字符串 全量输出因为 有些返回值过长会导致输出不全
				httpresponse.setContentLength(res.getBytes("utf-8").length);
				
				httpresponse.getWriter().write(res);
				
				httpresponse.getWriter().close();
				httpresponse.getWriter().flush();
				return;


		}else{
			byte[] content = wrapperResponse.getContent();
			response.getWriter().write(new String(content,"utf-8"));
			
			return;
		}

	}

	
	private static final String NO_CHECK = "whiteUrls";
	private static final String REDIRECT_PATH = "redirectPath";
	private List<String> noCheckList = new ArrayList<String>();
	@Override
	public void init(FilterConfig arg0)
			throws ServletException
	{

		String noChecks = arg0.getInitParameter(NO_CHECK);
		if(StringUtils.isNotBlank(noChecks)){
			if(StringUtils.indexOf(noChecks,",")!=-1){
				for(String no : noChecks.split(",")){
					noCheckList.add(StringUtils.trimToEmpty(no));
				}
			}else{
				noCheckList.add(noChecks);
			}
		}
	}
	private boolean check(String path) {
		if (noCheckList == null || noCheckList.size() <= 0)
			return false;
		for (String s : noCheckList) {
			if (path.indexOf(s) > -1) {
				return true;
			}
		}
		return false;
	}
	@Override
	public void destroy()
	{

	}

}
package com.system.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.system.utils.Base64;
import com.system.utils.MD5Utils;

public class EncryptDataUtils {

	/**
	 * 加密规则:
	 * 1- 将原始数据先转换成string类型 ,标记为o;
	 * 2- 将转换成string类型的原始数据先使用base64加密,此处是为了防止中文乱码。标记结果a;
	 * 3- 将第2步的结果使用md5加密,取加密结果的后半部分备用,标记结果b;
	 * 4- 将a截取前半部分备用,标记为c;
	 * 5- 随机码的后半部分 标记为x
	 * 6- 随机码的前半部分 标记为y
	 * 7- 加密标记 e
	 * 8- 将c与b拼接成字符串,作为最终的摘要信息
	 * 9- 最后的结果数据格式是: x+c+e+b+y|o
	 * @param data
	 * @param random 随机码
	 * @return 
	 */
	public static String Encrypt(Object data,String random){
		String str = "";
		String MD5Str = "";//定义MD5存放的数据
		String Base64Str = "";//定义base64存放的数据
		String isEncrpty = "0e5a0c2cca384a1b";//标记: 验证是否加密的标记  如果有这个标记  则返回值已经加密  否则 没有加密  bjhhwykjgfyxgs的MD5加密的16位
		if(data instanceof JSONObject){
			JSONObject ja = (JSONObject) data;
			str = JSONObject.toJSONString(ja);
		}else if(data instanceof JSONArray){
			JSONArray ja = (JSONArray) data;
			str = JSONArray.toJSONString(ja);
		}else if(data instanceof String){

			str = data+"";
		}
		if("".equals(str)){
			return null;
		}
		//定义报文加密格式 1- 原始报文使用Base64加密
		Base64Str = Base64.encode(str);  
		//1、获取MD5加密的原报文
		MD5Str = MD5Utils.md5(Base64Str);
		//2、获取MD5加密的原报文的后半部分
		MD5Str = MD5Str.substring(MD5Str.length()/2,MD5Str.length());
		//3、获取base64加密报文之后的前半部分
		Base64Str = Base64Str.substring(0,Base64Str.length()/2);
		//定义摘要算法  将base64加密报文之后的前一半和MD5加密之后原报文的后一半拼接起来使用MD5生成摘要  将加密标记放置在中间位置 用来前端识别
		//随机码的后半部分 + base64加密报文之后的前一半部分 + 加密标记 + MD5加密之后原报文的后一半部分 + 随机码的前半部分
		String remark = random.substring(random.length()/2) + Base64Str+isEncrpty+MD5Str + random.substring(0, random.length()/2);
		//截取屌密文部分的首尾字符
		String mi = remark.trim().substring(1,remark.length()-1) + "|"+ str;

		String returns = mi;
		
		return returns;
	}
	

	public static void main(String[] args) {
		
	}
}
package com.system.filter;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;


/**
 * 返回值输出代理类
 * 
 * @Title: ResponseWrapper
 * @Description:
 * @author Administrator
 * @date 2018-07-02
 */
public class ResponseWrapper extends HttpServletResponseWrapper
{

	private ByteArrayOutputStream buffer;

	private ServletOutputStream out;

	public ResponseWrapper(HttpServletResponse httpServletResponse)
	{
		super(httpServletResponse);
		buffer = new ByteArrayOutputStream();
		out = new WrapperOutputStream(buffer);
	}

	@Override
	public ServletOutputStream getOutputStream()
			throws IOException
	{
		return out;
	}

	@Override
	public void flushBuffer()
			throws IOException
	{
		if (out != null)
		{
			out.flush();
		}
	}

	public byte[] getContent()
			throws IOException
	{
		flushBuffer();

		
		return buffer.toByteArray();
	}

	class WrapperOutputStream extends ServletOutputStream
	{
		private ByteArrayOutputStream bos;

		public WrapperOutputStream(ByteArrayOutputStream bos)
		{
			this.bos = bos;
		}

		@Override
		public void write(int b)
				throws IOException
		{
			bos.write(b);
		}

		@Override
		public boolean isReady()
		{

			// TODO Auto-generated method stub
			return false;

		}

		@Override
		public void setWriteListener(WriteListener arg0)
		{

			// TODO Auto-generated method stub

		}
	}

}

相关base64加密和md5加密的js文件和java文件下载链接:

https://download.csdn.net/download/songyanfei1205/10514004

猜你喜欢

转载自blog.csdn.net/songyanfei1205/article/details/80885658