开放API网关实践(二) —— 重放攻击及防御

目录

开放API网关实践(二) —— 重放攻击及防御

如何设计实现一个轻量的开放API网关之重放攻击及防御

文章地址: https://blog.piaoruiqing.com/blog/2019/08/11/开放api网关实践之重放攻击及防御/

前言

上一篇文章(开放API网关实践(一) 中的接口设计提到 timestampnonce 两个参数的作用是用来放重放. 先抛出两个问题:

  • 什么是重放攻击
  • 如何防御重放攻击

什么是重放攻击(Replay Attacks)

什么是 重放 , 先举个例子:

打开浏览器的调试工具并访问一个网站, 在网络工具中找到一个请求并右键选择 Replay . 如图:

上述的 重放 操作是接口调试中比较常用的手段, 这种操作可以让我们跳过认证信息的生成过程, 直接重复发起多次有效的请求.

重放攻击 是一种黑客常用的攻击手段, 又称 重播攻击回放攻击 , 是指攻击者发送目的主机 已接收过的数据 , 以达到欺骗系统的目的, 主要用于身份认证过程, 破坏认证的正确性.

举个易懂的例子:

同样的数据

模拟重放攻击

实验器材

序号 名称 数量 备注
1 服务器x2 1 10.33.30.101 – 真实服务器
10.33.30.100 – 伪造服务器
2 域名 1 replay-test.piaoruiqing.com (10.33.30.101)
3 DNS 服务器 1 用来模拟 DNS 劫持

实验步骤

  1. 启动服务器, 请求接口并收到响应数据.
  2. 劫持DNS(在路由器中修改DNS服务器地址模拟劫持), 并拦截请求数据.
  3. 向服务器重复发送拦截到的数据(重放攻击).

过程记录

准备工作

DNS配置, 将域名 replay-test.piaoruiqing.com 指向内网中服务器的IP. 并启动服务器.

正常请求

使用 postman 发起一个正常的请求, 其中签名已在 Pre-request-script 中生成.

通过DNS劫持来拦截数据

修改内网的 dnsmasq 配置, 将域名 replay-test.piaoruiqing.com 指向伪造的服务器 10.33.30.100 .

此时向 replay-test.piaoruiqing.com 发起的请求便会被发送到伪造的服务器上(10.33.30.100), 手动将请求的数据保存下来. 由于请求带有签名, 且攻击者并没有拿到私钥, 故无法篡改请求, 但可以进行重放攻击. 如图, 伪造服务器已成功接收到请求数据:

[版权声明]

本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接: https://blog.piaoruiqing.com .

如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com .

重放请求

使用上一步保存下来的数据, 直接向真实服务器发送请求(带有签名数据). 如图:

事实上, 签名、加密等手段并不能防御重放攻击, 因为攻击者拦截到的数据已是正确的请求数据, 即使无法破解其内容, 也可以重放向服务器发送原数据以达到欺骗的目的.

如何防御重放攻击

百度百科

加随机数
加时间戳
加流水号

在实际使用中, 常将1和2结合使用, 时间戳有效期内判断随机数是否已存在, 有效期外则直接丢弃.

重放攻击防御实践

我们采取 时间戳 + 随机数 的方式来实现一个简单的重放攻击拦截器. 时间戳和随机数互补, 既能在时间有效范围内通过校验缓存中的随机数是否存在来分辨是否为重放请求, 也能在缓存失效后(缓存有效时间和时间范围一致)通过时间戳来校验该请求是否为重放. 如图:

代码如下:

@Resource
private ReactiveStringRedisTemplate reactiveStringRedisTemplate;

private ReactiveValueOperations reactiveValueOperations;

@PostConstruct
public void postConstruct() {
    reactiveValueOperations = reactiveStringRedisTemplate.opsForValue();
}

@Override
protected Mono doFilter(ServerWebExchange exchange, WebFilterChain chain) {
    // 此处的`ATTRIBUTE_OPEN_API_REQUEST_BODY`是前面过滤器存入的
    OpenApiRequest body 
        = exchange.getRequiredAttribute(ATTRIBUTE_OPEN_API_REQUEST_BODY);
    if (!ObjectUtils.allNotNull(body, body.getTimestamp(), body.getNonce())) {
        return fail(exchange);
    }
    Long gmt = System.currentTimeMillis();
    // (一)
    if (gmt + effectiveTimeRange  body.getTimestamp()) {
        return fail(exchange);
    }
    // (二)
    return reactiveValueOperations.setIfAbsent(MessageFormat.format(
            KEY_REPLAY_NONCE, body.getAppId(), body.getNonce()),
            String.valueOf(System.currentTimeMillis()),
            Duration.ofMillis(effectiveTimeRange * 2L))
        .log(LOGGER, Level.FINE, true)
        .flatMap(approved -> approved ? 
                 chain.filter(exchange) : fail(FORBIDDEN, exchange)
            );
(一)
(二)

结语

重放攻击防御的关键点:

  • 记录请求标识并缓存, 接受请求时校验, 拒绝重放, 即将 nonce 存入缓存, 拒绝相同的 nonce
  • 随机数的方式可能造成过多的缓存, 故需要配合时间戳进行过滤, 时间戳不在有效范围内的一律拒绝.

重放攻击是一种常用且有效的攻击手段, 其危害不可忽视, 尽管可以通过业务层面来保障数据的正确性, 但依旧会给系统造成不必要开销, 在网关层过滤掉重放请求是一个不错的选择.

© 2019,朴瑞卿.

[版权声明]

本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接: https://blog.piaoruiqing.com . 如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com .

2+