会话重放攻击与完整性校验解决⽅案
简介:
攻击者发送⼀个⽬的主机已经接收过的包,特别是在认证的过程中,⽤于认证⽤户⾝份所接收的包,来达到欺骗系统的⽬的,主要⽤于⾝份认证过程,破坏认证的安全性。也可利⽤系统中POST请求数据包未针对单个请求设置有效的验证参数,导致会话请求可以重放,⽆限制的向数据库中插⼊海量数据,或⽆限制的上传⽂件到系统中,造成资源浪费。
解决⽅案:
1. 后端⽣成当前系统时间
//⽤于获取当前系统时间,以毫秒为单位
@RequestMapping(value="queryCurrentTime",method=RequestMethod.GET)
public Map<String,Object>queryCurrentTime(){
Map<String,Object> map  =new HashMap<>();
map.put("code","SUCCESS");
map.put("message","成功!");
map.put("data",System.currentTimeMillis());
}
2. 在vue中,request.js中统⼀进⾏处理,
import request from'@/utiils/request.js'
import CryptoJS from'crypto-js'
//随机数
let guid=function(){
return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){
var r=Math.random()*16|0,
v = c =='x'? r:(r &0x3|0x8);
String(16);
})
}
// 请求
quest.use(
async(config)=>{
// 在发送请求之前做⼀些事情
//此处只对post做处理, 如若对get请求做处理,将此post校验if去掉即可
hod =="post"){
// 防⽌死循环
if(url.indexOf('queryCurrentTime')!==-1){
return config
}
const req ={
url:'localhost:8888/queryCurrentTime',
method:'get',
}
//async await 同步
await request(req).then(response =>{
// 服务器获取时间戳
let timestamp = data.data
const uuid =require('uuid')
// 在localStorage中获取盐值
// let saltValue = Item('uId')
let saltValue ='CURRENTIME'
let saltValue ='CURRENTIME'
let params =''
hod ==='post'&& config.data){
params =decodeURIComponent(config.data)
}else{
if(config.params){
let paramsKeys =[]
paramsKeys = Object.keys(config.params)
// 根据key进⾏排序
paramsKeys = paramsKeys.sort()
let paramsValues =[]
paramsKeys.forEach(value =>{
if(typeof config.params[value]!=='object'){
paramsValues.push(config.params[value])
}
})
params = paramsValues.join('-')
}
}
// console.log(params)
// ⽣成随机数uuid
let uId = uuid.v4()
// MD5签名
let sign = CryptoJS.md5(timestamp + uId + params + saltValue)    config.headers.timestamp = timestamp
config.headers.sign = sign
playToken = uId
})
}
return config
},
error =>{
// 做⼀些请求错误
console.log(error)
ject(error)
}
)
3. 配置过滤器的启动顺序
@Configuration
public class FilterConfig{
@Autowired
private CommitTokenService commitTokenService ;
//此处解决RedisTemplate对象⽆法注⼊的问题 !! 将过滤器定义为bean对象,交由spring管理
//过滤器属于servlet范围,⽆法直接注⼊对象
//springboot关于或者过滤器⽆法注⼊bean的问题 ? 参考 segmentfault/a/1190000012107467
@Bean
public FormFilter formFilter(){
return new FormFilter();
}
@Bean
public FilterRegistrationBean filterRegistrationBean (){
FilterRegistrationBean filterRegistrationBean =new FilterRegistrationBean ();
filterRegistrationBean.setOrder(3);
filterRegistrationBean.setFilter(formFilter());
filterRegistrationBean.setName("FormFilter");
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
4. 异常处理类
public class ExceptionCommonUtils{
pulbic static String getId(){
place("-","");
}
//⽤来响应信息
public static void responseMessage(HttpServletResponse response,Integer code,String message)throw IOException{  Map<String,Object> map =new HashMap<>();
map.put("code",code);
map.put("message",message);
response.setContentType(text/json;charset=utf-8);
JSONObject fromObject = JSONObject.formObject(map);
PrintWriter pw = Writer();
pw.String());
pw.flush();
pw.close();
}
}
5. 通过MD5加密,⽣成sign
public class IntegrityChecking{
//盐值
private static final String MARKER ="CURRENTTIME";
//获取sign值
public static String getSign(String timestamp,String raToken,String data){
StringBuilder sb =new StringBuilder ();
sb.append(timestamp);
sb.append(raToken);
sb.append(data);
sb.append(MARKER);
return DigestUtils.String().getBytes());
}
8. ①获取前台传⼊的时间,token以及前台经过MD5加密的sign值,
②时间+token+盐值 进⾏MD5加密和sign值对⽐,不⼀致说明被数据篡改,触发完整性校验
③计算时间差, ⼩于等于60秒内的sign放⼊到redis中存储有效时间为1⼩时, 每次请求都去判断key是否存在,如存在,则是重放攻击
④如请求时间超过60秒,则判定为请求超时,也是重放攻击
public class FormFilter implements Filter{
@Autowired
private RedisTemplate redisTemplate;
private static final long EXPIRTIME =3600L;//1⼩时
private static final String PREFIX ="WQ";
public FormFilter (){}
@Override
public void doFilter(ServletRequest arg0,ServletResponse arg1,FilterChain arg2)throws IOException,ServletException{
HttpServletRequest req =(HttpServletRequest)arg0;
HttpServletResponse  response =(HttpServletResponse)arg1;
//如果是获取时间戳的请求,直接放⾏,或者是get请求进⾏防重放,完整性校验处理
String url = RequestURI();
if(url.indexOf("queryCurrentTime")!=-1||"GET".Method())){
arg2.doFilter(req,response);
}else{
//随机数
String timestamp = Header("timestamp");
//token
String raToken = Header("replayToken");
//前台MD5⽣成的sign密⽂字符串
String sign = Header("sign");
try{
//防⽌timestamp raToken被篡改
if(!"".equals(timestamp)&&!"".equals(raToken)&&!"".equals(sign)&& null != timestamp && null != raToken && null != sign){
String strString ="";
StringBuilder str =new StringBuilder();
Enumeration<String> param = ParameterNames();
if("POST".Method())){
while(param.hasMoreElements()){
String key = String.Element());
String value = Parameter(key);
str.append(key).append("=").append(value).append("&");
}
}else if("GET".Method())){
while(param.hasMoreElements()){
String key = String.Element());
String value = Parameter(key);
str.append(value).append("-");
}
}
//去掉最后⼀个字符
if(!"".String())&& str.length()>0){
strString = String().substring(0,str.length()-1);
}
// 对⽐sign值
js获取json的key和value
String parSign = Sign(timestamp,raToken,strString);
if(!sign.equals(parSign)){
throw new RunTimeException("请勿中途修改数据,触发完整性校验!");
}
//计算时间差
long parseInt = Long.parseLong(timestamp);
long currentTimeMills = System.currentTimeMills();
long reqTime =(currentTimeMills - parseInt );
//60秒内的sign放⼊redis中
if(reqTime <=60000){
//检查key是否存在
if(!redisTemplate.hasKey(PREFIX + sign)){
//设置变量值的过期时间
redisTemplate.opsForValue().set(PREFIX  + sign,0,EXPIRTIME,TimeUnit.SECONDS);
}else{
throw new RunTimeException("请勿重复提交表单!");
}
}else{
//⼤于60秒,判为请求超时
throw new RunTimeException("请求超时,请勿重复提交表单!");
}
arg2.doFilter(req,response);
}else{
throw new RunTimeException("TIMESTAMP.TOKEN.SIGN为空,请勿重复提交表单或触发完整性校验!"); }
}catch(RunTimeException e){
}catch(Exception e){
}
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。