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

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

  •  
  •   Richard14 · 2021-11-23 01:38:57 +08:00 · 2587 次点击
    这是一个创建于 1092 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

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

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

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

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

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

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

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

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

    LZ 可以自己搭建个 MIMT 测试环境,然后尝试中间人攻击,这样可以更好的了解攻击者手法。
    letitbesqzr
        22
    letitbesqzr  
       2021-11-23 17:19:13 +08:00
    @Chad0000
    我觉得时间戳和随机数并不是同一个功能。

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

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

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

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

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

    两者结合,既避免了时间戳 5 分钟的窗口期,又避免了随机字符串的存储量很大
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5635 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 01:29 · PVG 09:29 · LAX 17:29 · JFK 20:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.