【Java】如何有效防止API的重放攻击?API接口防止参数篡改?

今天药忘吃喽~ 2023-10-13 17:24 108阅读 0赞

文章目录

  • 前言
  • 一、API接口常见的安全防护要做到主要有以下几点:
  • 二、请求参数防篡改
  • 三、防止重放攻击
    • 3.1、基于timestamp的方案
    • 3.2、基于nonce的方案
    • 3.2、基于timestamp和nonce的方案
    • 3.3、微信公众号如何保证消息不会被重放攻击

前言

API重放攻击(Replay Attacks)又称为重播攻击、回放攻击。它的原理就是把之前窃听到的数据原封不动的重新发送给接收方。HTTPS并不能防止这种攻击,虽然传输的数据都是经过加密的,窃听者无法得到数据的准确定义,但是可以从请求的接收方地址分析这些数据的作用。比如用户登录请求时攻击者虽然无法窃听密码,但是却可以截获加密后的口令然后将其重放,从而利用这种方式进行有效的攻击。

所谓重放攻击就是攻击者发送一个目的主机已经接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,重放攻击是计算机世界黑客常用的攻击方式之一。

一次HTTP请求,从请求方到接收方中间要经过很多路由器和交换机,攻击者可以在中途截获请求的数据。假设在一个网上存款系统中,一条消息表示用户支取了一笔存款,攻击者完全可以多次发送这条消息而偷窃存款。
重放是二次请求,如果API接口没有做到对应的安全防护,将可能造成很严重的后果。

一、API接口常见的安全防护要做到主要有以下几点:

  • 防止sql注入
  • 防止xss攻击
  • 防止请求参数被篡改
  • 防止重放攻击

主要防御措施可以归纳为两点:

  • 对请求的合法性进行校验
  • 对请求的数据进行校验

防止重放攻击必须要保证请求仅一次有效。需要通过在请求体重携带当前请求的唯一标识,并且进行签名防止被篡改。所以防止重放攻击需要建立在防止签名被篡改的基础之上。

二、请求参数防篡改

采用https协议可以将传输的明文进行加密,但是黑客仍然可以截获传输的数据包,进一步伪造请求进行重放攻击。如果黑客使用特殊手段让请求方设备使用了伪造的证书进行通信,那么https加密的内容也会被解密。
在API接口中我们除了使用https协议进行通信外,还需要有自己的一套加解密机制,对请求的参数进行保护,防止被篡改。

过程如下:

  • 客户端使用约定好的秘钥对传输的参数进行加密,得到签名值signature,并且将签名值也放入请求的参数中,发送请求给服务端
  • 服务端接收到客户端的请求,然后使用约定好的秘钥对请求的参数(除了signature以外)再次进行签名,得到签名值autograph。
  • 服务端比对signature和autograph的值,如果对比一致,认定为合法请求。如果对比不一致,说明参数被篡改,认定是合法请求。

因为黑客不知道签名的秘钥,所以即使截获到请求数据,对请求参数进行篡改,但是却无法对参数进行前面,无法得到修改后参数的签名值signature。
签名的秘钥我们可以使用很多方案,可以采用对称加密或者非对称加密

三、防止重放攻击

3.1、基于timestamp的方案

每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。因为一次正常的HTTP请求,从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间比较,是否超过了60s,如果超过了则认为是非法请求。

一般情况下,黑客从抓包重放请求耗时远远超过了60s,所以此时请求中的timestamp参数已经失效了。

如果黑客修改timestamp参数为当前的时间戳,则signature参数对应的数字签名就会失效,因为黑客不知道签名秘钥,没有办法生成新的数字签名。

但是这种方式的漏洞也是显而易见,如果在60s之内进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效。

3.2、基于nonce的方案

nonce的意思是仅一次有效的随机字符串,要求每次请求时,该参数要保证不同,所以该参数一般与时间戳有关,我们这里为了方便起见,直接使用时间戳的16进制,实际使用客户加上客户端的ip地址,mac地址等信息做个哈希之后,作为nonce参数。

我们每次将请求的nonce参数存储到一个”集合“中,可以json格式存储到数据库或缓存。

每次处理HTTP请求时,首先判断该请求的nonce是否在该集合中,如果存在则认为是非法请求。

nonce参数在首次请求时,已经被存储到了服务器上的集合中,再次请求会被识别并拒绝。

nonce参数作为数组签名的一部分,是无法篡改的,因为黑客不清楚token,所以不能生成新的sign。

这种方式也有很大的问题,那就是存储nonce参数的集合会越来越大,验证nonce是否在集合中的耗时会越来越长。我们不能让nonce集合无限大,所以需要定期清理该集合,但是一旦该集合被清理,我们就无法验证被清理的nonce参数了。也就是说,假设该集合平均1天清理一次的话,我们抓取到的该url虽然当时无法进行重放攻击,但是我们还是可以每隔一天进行一次重复刚攻击的。而且存储24小时内,所有请求的nonce参数,也是一笔不小的开销。

3.2、基于timestamp和nonce的方案

nonce的一次性可以解决timestamp参数60s的问题,timestamp可以解决nonce参数集合越来越大的问题。防止重放攻击一般和防止请求参数被篡改一起做。请求的Headers数据如下图所示。
在这里插入图片描述

我们在timestamp方案的基础上,加上nonce参数,因为timestamp参数对于超过60s的请求,都认为是非法请求,所以我们只需要存储60s的nonce参数集合即可。
API接口验证流程:

  1. String token = request.getHeader("token");
  2. String timestamp = request.getHeader("timestamp");
  3. String nonceStr = request.getHeader("nonceStr");
  4. String url = request.getHeader("url");
  5. String signature = request.getHeader("signature");
  6. if(StringUtil.isBlank(token) || StringUtil.isBlank(timestamp) || StringUtil.isBlank(nonceStr) || StringUtil.isBlank(url)
  7. || StringUtil.isBlank(signature))
  8. {
  9. return;
  10. }
  11. UserTokenInfo userTokenInfo = TokenUtil.getUserTokenInfo(token);
  12. if(userTokenInfo == null){
  13. return;
  14. }
  15. if(!request.getRequestURI().equal(url)){
  16. return;
  17. }
  18. if(DateUtil.getSecond()-DateUtil.toSecond(timestamp) > 60){
  19. return;
  20. }
  21. if(RedisUtils.haveNonceStr(userTokenInfo,nonceStr)){
  22. return;
  23. }
  24. String stringB = SignUtil.signature(token, timestamp, nonceStr, url, request);
  25. if(!signature.equals(stringB)){
  26. return;
  27. }
  28. RedisUtils.saveNonceStr(userTokenInfo,nonceStr,60);

3.3、微信公众号如何保证消息不会被重放攻击

使用微信公众平台的接口需要在微信公众平台设置token。这里假设token是不会被攻击者知道的,相当于一个PSK(Pre Shared Key)微信发消息会有三个参数:signature、timestamp和nonce。
验证是否为微信发送的消息流程如下:

  1. $signature = $_GET["signature"];
  2. $timestamp = $_GET["timestamp"];
  3. $nonce = $_GET["nonce"];
  4. $token = TOKEN;
  5. // 按照$token,$timestamp,$nonce的顺序组成数组
  6. $tmpArr = array($token, $timestamp, $nonce);
  7. // 按照字典序排序
  8. sort($tmpArr, SORT_STRING);
  9. // 将排序后的数组串成字符串
  10. $tmpStr = implode( $tmpArr );
  11. // 用sha1计算签名
  12. $tmpStr = sha1( $tmpStr );
  13. // 校验签名
  14. if( $tmpStr == $signature ){
  15. return true;
  16. }else{
  17. return false;
  18. }

这里在使用的时候,这里针对消息的重放攻击的防护应该检查nonce值是否已经存在,如果已经存在可能为非法通知,按道理来讲也应该检查这里timestamp和当前时间比较是否已经过了一定的时间,但是这里在微信公众号开发文档中并没有说明,这里其实应该是需要验证消息是否是否在同一个时间戳内的,如果接收到的消息距离发送的时间已经超过1s就可以直接丢弃了,当然在网络不好的情况下会造成丢消息。

另外一个这里针对重试或者幂等性的要求上要做到解密后消息中去,比如发起重试使用同样的消息id即可。虽然每次的nonce值是不一样的,但是消息的id是一样的,就可以区分出来是否为同一个消息的重试发送。

这里一般的做法是抽出一个前置网关,做消息的解密和加密,后端服务进行消息的处理,消息的处理上对重试带来的幂等性问题做处理就可以了。

发表评论

表情:
评论列表 (有 0 条评论,108人围观)

还没有评论,来说两句吧...

相关阅读

    相关 【添加时间戳防止攻击

    如过客户端在向服务端接口进行请求,如果请求信息进行了加密处理,被第三方截取到请求包,虽然第三方无法解密获取其中的数据,但是可以使用该请求包进行重复的请求操作。如果服务端不进行防

    相关 API接口

    我们在设计API接口时,最怕的莫过于同一个接口给用户截获重放提交,那什么是重放提交:对同一个请求发送多次到后台,对系统产生异常影响。 应对的策略有: 1使用时间戳time