项目安全问题,如何防止会话重放攻击和数据篡改

项目到后期都会遇到安全测试问题,本篇整理一下博主的项目怎么解决会话重放和数据篡改问题的。

一般我们的项目中涉及增删改查操作时,前台发起请求后,攻击者利用抓包工具恶意修改客户端的请求参数,就会使服务器执行攻击者想要执行的操作。

对于增删改操作,攻击者抓包后可以无限次发起同样的请求,这样就会使服务器产生很多无用数据甚至崩溃,这就是所谓的会话重放。而对于查询操作一般不会有这个问题,因为查询并没有改变数据(既然都能查到了随你怎么查呗),但是如果测试人员要求改,那我们苦命的开发只能改喽(心中MMP?)。

博主一开始只针对增删改操作做了修改,修改方法:

首先,写一个自定义注解,对controller中需要作验证的方法上加上注解,代码:

public class TokenInfo {
	/**
	 * 用户token信息
	 */
	private static Map<String,String> userToken=new HashMap<String,String>();

	public static String getUserToken(String userId){
		if(!userToken.containsKey(userId)){
			userToken.put(userId, "");
		}
		return userToken.get(userId); 
	}
	
	public static void setUserToken(String userId,String token){
		userToken.put(userId, token);
	}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TokenAnnotation {
	public abstract boolean open();
}

用户登录后,系统第一次访问服务器时(这里由于博主的项目登录系统调用的接口,无法在登录方法中操作),后台生成一个随机值(TOKEN),存到用户对象(USER)中,并返回前台,前台存到sessionStorage中(sessionStorage类似cookie,但存放的数据比cookie多,浏览器关闭时清空),有增删改操作并再次发起请求时(默认使用ajax请求,这里就需要注意前期代码的规范问题了),使用ajaxSend方法将sessionStorage中token值取出,加到参数中传递到后台。后台写一个拦截器,获取请求的方法以及请求参数,如果请求的方法加了注解即验证token,从用户对象中取出token值与前台传过来的token比较,如果相同则验证通过,生成新的token值保存并返回前台保存(这里需要对controller中方法的返回值作修改,加上token值,这里又涉及到代码的规范问题了?),如此循环,如果验证失败则作相应处理。代码:

/**
 * token验证
 * method为需要访问的method对象,自己想办法获取
 */
public boolean tokenAnnotation(Method method,HttpServletRequest request,HttpServletResponse response){
    if(method.isAnnotationPresent(TokenAnnotation.class)){
        TokenAnnotation tokenAnn = method.getAnnotation(TokenAnnotation.class);
        boolean open = tokenAnn.open();
        if(open){
            //得到用户对象信息,这里就不写全了
            user = ······
            String token = request.getParamter("TOKENVALUE");
            String trueToken = TokenInfo.getUserToken(user.getUserId());
            if(token!=null&&token.equals(trueToken)){
                //更新token
                WebUtil.openToken(user);
            }
        }    
    } 
}

public static void openToken(User user){
    //设置token
    String tokenValue = getNewToken();
    TokenInfo.setUserToken(user.getUserId(),tokenValue);
    user.setTokenValue(tokenValue);
}

public static String getNewToken(){
    //生成10位数的token
    String token = "";
    Random ran = new Random();
    for(int i=0;i<10;i++){
        token+=ran.nextInt(10);
    }
    return token;
}

controller中方法这里就不贴了,总体来说不是是map就是json,直接putTOKEN值就行了,前台ajax响应后,利用ajaxComplete,将token值放到sessionStorage中。

另外需要说明的一点,所有的ajax请求需要设置为同步,如果ajax中有嵌套另一个ajax,可以再在前后台保存一个token修改次数(requestStamp),ajaxComplete方法作相应修改,相应的后台请求的方法返回时,REQUESTSTAMP需要加1并且返回前台

var requestStamp = xhr.responseJSON.REQUESTSTAMP;
if(requestStamp > sessionStorage.STAMP){
    sessionStorage.STAMP = requestStamp ;
    sessionStorage.TOKENVALUE = xhr.responseJSON.TOKENVALUE ;
}

至此增删改的token验证功能已经做好了,如果不涉及查询一切都没有问题,然而···

好吧,不扯了,上干货。对于查询操作,最多有个参数篡改问题,对于这个问题使用上面的token验证是无法起作用的。这个问题困扰了博主很久,最后使用了最简单粗暴的方法,加时间戳,直接防止抓包!

具体方法:前台发请求的时候获取本地时间,加到参数中并加密,传到后台,拦截器拦截到参数中带有时间戳的,解密,与服务器本地时间作比较,时间差大于2000毫秒的则验证不通过,这里需要注意客户端与服务端本身就有时间差,我的做法是系统第一次访问后后台返回本地时间,前台接收后与本地时间相减存到sessionStorage中,下次发起请求时发送的时间参数加上这个时间差。

//系统进入的第一个页面,将初始token值,初始stamp,服务器时间保存
sessionStorage.TOKENVALUE = "${token}";
sessionStorage.STAMP = 0;
var server_time = "${server_time}";
var user_time = (new Date()).getTime();
//发请求加上时间戳参数时加上这个时间差再加密
sessionStorage.DTIME = server_time - user_time;
int show = 0;
int show2 = 0;
int show3 = 0;
for (String key : paramMap.keySet()) {
	String[] values = paramMap.get(key);
	for (int i = 0; i < values.length; i++) {
	    String param = values[i];
		if(key.equals("BF1DC")){
			show++;
			try {
				Des desObj = new Des();
				String dec = desObj.strDec(param, "1", "2", "3");
				String thatTime = dec.substring(1);
				Long lostTime = System.currentTimeMillis()-Long.parseLong(thatTime);
				if(lostTime>2000){
					show3++;	
				}
			} catch (Exception e) {
			    // TODO: handle exception
			    show2++;
			}
					
		}
	}
}
if(show>0&&(show2>0||show3>0)){
    String head = request.getHeader("X-Requested-With");
    if(!StringUtil.isEmpty(head)){
        //ajax请求则将错误信息返回到data中,ajax success方法中作相应处理
    }else{
        //普通请求直接返回错误页面
    }
}

写到这里终于大功告成了,那么问题来了,为什么一开始不用时间戳呢?博主只能说,程序员都是一步一个坑爬过去的???,还有最重要的一点,项目初期就要严格规范下代码书写规范,便于维护。最最最后一点,老项目碰不得???。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值