V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Richard14
V2EX  ›  问与答

网络请求中使用随机数避免重放攻击的原理是什么?

  •  
  •   Richard14 · 62 天前 · 1672 次点击
    这是一个创建于 62 天前的主题,其中的信息可能已经有所发展或是发生改变。

    注意到所有的这些云平台提供的 api ,调用的过程中都需要生成一个随机数,然后根据随机数生成签名。调用百度翻译、腾讯翻译 api 的时候都是如此。据说是为了避免被重放攻击。那么它的原理是什么呢?

    第一次能请求成功的话,签名就是合法的,如果这时候第二次进行了完全相同的请求,服务器怎么知道该请求属于重放呢?

    疑问一:是默认不同时间生成的随机数完全不同吗?有没有可能刚好两次临近请求生成了相同的随机数?比如一秒内刚好两次生成 9527 这个数。

    疑问二:后端如何拦截?难道要维护一个队列,每个 ID 最近三分钟内请求的随机数都有哪些?类似这种的?如果这么搞的话成本不会爆炸吗?

    25 条回复    2022-01-09 21:05:22 +08:00
    binux
        1
    binux  
       62 天前 via Android
    > 调用的过程中都需要生成一个随机数
    哪里说要生成一个随机数了?
    Rocketer
        2
    Rocketer  
       62 天前 via iPhone
    虽然没看过文档,但感觉用随机数不如直接用时间戳,超时忽略即可。

    至于说维护一个 request id (随机数)的库就更不太可能了,分布式系统同步这个数据的成本可不低。
    Chad0000
        3
    Chad0000  
       62 天前
    就是避免攻击者使用同一个请求。一般不会使用随机数而使用时间戳,比如允许有 5 分钟时间差,这样复制原请求的攻击只能限定在一定时间内。严格的话可以缓存这个时间戳(比如根据日志分析某个 Client 可能被攻击,标记此 Client 需要检查)
    binux
        4
    binux  
       62 天前 via Android
    看了下百度是用随机数的 salt ,腾讯是 5 分钟有效的 timestamp 。
    但是百度签名用的是 md5 ,加 salt 是为了防止选择明文攻击吧。
    Chad0000
        5
    Chad0000  
       62 天前
    @binux #4 跟明文攻击没关系,验证算法比如是:MD5 ( Reqeust Data + KEY + Salt ),Salt 是时间戳,Key 是自己的密码。这种方式中间人只能重复这个请求,直到这个请求不被认可,比如重复使用 Salt 或者 Salt 过期(如果是时间戳)。
    binux
        6
    binux  
       62 天前
    @Chad0000 首先百度没有说 Salt 是时间,它也不会当作时间处理,其次如 LZ 所说,它存储 salt 太浪费资源了,或者 salt 有随机生成重复导致请求失败的可能。
    所以应该不是为了避免重放攻击。
    Veneris
        7
    Veneris  
       62 天前
    我之前实现过这个逻辑,https://github.com/viticis/API-Signed

    其实就是 签名 = 算法 ( appId + 接口 + 时间戳 + 随机数 )
    四个参数只要有一个有一点点变动,签名就完全是不一样的了。

    那么可以认为,同一个 appId (即同一个第三方用户),在同一毫秒内,调用同一个接口,使用了同一个随机数的情况下,大概率为重放攻击,只需要判断 appId -> 签名 在缓存中有无对应关系即可。
    Richard14
        8
    Richard14  
    OP
       62 天前   ❤️ 1
    @Veneris 所以还是要保存一个 appid 到签名的对应关系?是保存 appid 的最近请求的所有签名,然后每次新请求都挨个匹配?感觉成本会非常爆炸。否则的话难道只保存最近一次的请求?那么如果攻击者通过某种途径截取到了身份认证的封包,等一分钟以后重新发送,攻击不就成功了吗
    Veneris
        9
    Veneris  
       62 天前
    @Richard14 #8 可以把这几个参数拼接成 key ,签名为 value ,使用 redis 做判断 key 存不存在就可以了,我们当时的业务来说,没什么太大开销。第二个问题的话,除了 签名校验,重放攻击校验,还有一个时间校验,要求 调用发起方 与 服务器 时差在 10s 内,两个目的,一个就是防止你说的这个情况,一个是 redis 里的 key 可以设置 10s 过期,防止大量堆积。

    步骤来说,首先判断时间,时差过大直接拒绝,时差在误差以内,通过拼接的 key 是否在 redis 中存在来判断是否重放攻击,是的话拒绝,否的话,再校验签名,在转发到对应的业务层。
    LeeReamond
        10
    LeeReamond  
       62 天前
    @Veneris 懂了,所以你的意思是防止重放攻击的手段是 redis 上搞个队列,里面存全服所有的( appId + 接口 + 时间戳 + 随机数)请求,比如维护一个三分钟队列之类的,这样队列也不会特别吃内存,搜索开销也不大,这样?
    Veneris
        11
    Veneris  
       62 天前
    @LeeReamond #10 以我们当时的业务来说,没这么多第三方调用,加上一般这种时间戳会要求 10s ,不会说客户端和服务器时差 3 分钟还允许调用,所以 redis 其实压力不大,但我也不知道大厂的这种业务是什么架构,只是说实现了一样的功能
    LeeReamond
        12
    LeeReamond  
       62 天前
    @Veneris 感觉问题不大,稍微切一切分一分,感觉大厂也能接受这种架构,挺合理的
    sujin190
        13
    sujin190  
       61 天前
    既然你已经想到不能避免重放,那就别怀疑了,没有人可以超越物理限制的,事实上这个是用来权限验证的,你看签名需要密钥吧,而这个不会放在参数里,一般没有防止重放攻击的作用,想防重放可以加入时间参数,签名校验成功后检查超过一定时间拒掉就是了
    Chad0000
        14
    Chad0000  
       61 天前
    @sujin190 #13 其实也能在指定场景下完全避免重放。我这边就是时间戳,同时限定后来的请求时间戳要比之前的大,这个数据 Redis 缓存即可。在他们请求不太频繁的情况下随便调用不影响,太频繁了调用方就需要控制调用顺序啦 - 一举两用。这个时间戳其实也就是计数器,用时间戳的好处是绝大部分情况下(非高频)无需对方全局管理这个值。
    Greenm
        15
    Greenm  
       61 天前
    想不通的话参考一下验证码和 TOTP ,加上几分钟内有效就是为了防止重放
    clf
        16
    clf  
       61 天前
    可能是用来算 QPS 的。
    documentzhangx66
        17
    documentzhangx66  
       61 天前
    首先,请求加 ID ,并且每次请求要更换 ID ,是为了实现客户端因为一些问题,比如网络故障,导致用户重复点击业务办理按钮之类的事情,导致重复办理。

    一般情况下,直接用一个自增 ID 就行了。
    BeijingBaby
        18
    BeijingBaby  
       61 天前
    背后是分布式系统。
    加个随机数,是为了请求过程中重试,防止重复处理消息。
    Zy143L
        19
    Zy143L  
       61 天前 via Android
    最经典的就是时间戳
    046569
        21
    046569  
       61 天前
    “第一次能请求成功的话,签名就是合法的,如果这时候第二次进行了完全相同的请求,服务器怎么知道该请求属于重放呢?”
    => 服务器上保存了有效 ID ,请求后立即删除。第二次请求由于 ID 不合法而丢弃。

    “疑问一”
    => 各编程语言一般使用伪随机数,即便是相同时间也会生成不同的随机数,是统计学意义上的随机。你可以改用用户输入键盘上字母时间,字母分布等实现更好的随机数。

    “疑问二”
    => 如果使用时间过期的策略,那么在有效期内是 **可以** 攻击成功的。成本可以依靠统一的 No SQL 数据库解决。主要目的是不要访问主数据库。

    虚拟场景:
    攻击者 A 让受害者 B 执行一笔转账且截获了 B 的令牌,A 重新发起转账,此时若使用时间戳策略,A 可以预测未来的时间戳,提前生成并发送造成多次转账,攻击成功。允许五分钟你猜猜能转账成功多少次。

    LZ 可以自己搭建个 MIMT 测试环境,然后尝试中间人攻击,这样可以更好的了解攻击者手法。
    letitbesqzr
        22
    letitbesqzr  
       61 天前
    @Chad0000
    我觉得时间戳和随机数并不是同一个功能。

    时间戳 可以限制请求是 5 分钟内的请求。但是并没办法防止重放攻击啊?意思是允许 5 分钟内的重放? 也不可能限制某个时间戳 只能请求一次吧?
    Chad0000
        23
    Chad0000  
       61 天前
    @letitbesqzr #22 校验码 = md5(AppID + RequestData + timestamp + key),然后规定 timestamp 不能重复且必须大于上一次请求,这个检验通过 Redis 实现,只需要存一个就行,缓存的 Key 可以是:AppID 加上固定前缀这种。每次请求验证通过后即可更新此值。甚至如果你愿意你还可以保存在 DB 中,通过 update Table set [email protected] where Id=AppId and LastTimeStamp<@0 这种更新来确保它是递增的。

    要不要严格判断取决于业务被重放的代价。
    Chad0000
        24
    Chad0000  
       61 天前
    @letitbesqzr #22 这个 timestamp 实际上与计数器没区别,之所以使用 timestamp 是因为绝大部分场景下调用方使用频率低,推荐他们使用 timestamp 直接无需保存计数器,方便直接。调用多了就考虑让他们使用队列或全局计数器当 timestamp 甚至 IP 白名单适当放宽都可。
    daimubai
        25
    daimubai  
       14 天前
    timestamp+UUID(随机字符串)
    前端:签名时将 timestamp 和 UUID 加入一起签名

    后端:
    首先比较 timestamp ,如果当前时间和 timestamp 相差 5 分钟直接拒掉

    如果 timestamp 没毛病,去 redis 查这个 UUID 是否存在,存在的话直接拒掉

    UUID 不存在,将 UUID 放入 redis 中,过期时间设置为 5 分钟,同时认为当前请求不是重放攻击。

    两者结合,既避免了时间戳 5 分钟的窗口期,又避免了随机字符串的存储量很大
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3515 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 01:28 · PVG 09:28 · LAX 17:28 · JFK 20:28
    ♥ Do have faith in what you're doing.