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

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

前言

上一篇文章(开放API网关实践(一)中的接口设计提到timestampnonce两个参数的作用是用来防重放. 本文就重放攻击及其防御进行探讨. 先抛出两个问题:

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

什么是重放攻击(Replay Attacks)

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

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

Chrome请求重放

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

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

举个易懂的例子:

  • 服务端提供了打款接口, 用户A向服务端请求发起一次打款5元的操作(附带了签名并进行了加密), 服务端接收到了数据并正确打款给用户B.
  • 但这个请求被黑客拦截到(可能就是用户B干的 ( ̄▽ ̄)"), 黑客将请求原封不动地向服务器发送, 服务器多次错误地打款给用户B. (当然, 这些都是是建立在服务端的付款没做幂等等防范措施、安全级别较低的前提下)
  • 尽管A发起的请求有签名和加密, 但B无需破解这个数据, 只是将同样的数据重复发给服务器就能达到欺骗的目的.

replay-attacks

模拟重放攻击

实验器材

序号 名称 数量 备注
1 服务器x2 1 Mac: 10.33.30.101 - 真实服务器
Windows: 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. 并启动服务器.

dns-conf

正常请求

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

before-pre

通过DNS劫持来拦截数据

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

dnsmasq-conf

nslookup

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

get-request

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

重放请求

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

重放

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

如何防御重放攻击

百度百科
  1. 加随机数: 该方法优点是认证双方不需要时间同步,双方记住使用过的随机数, 如发现报文中有以前使用过的随机数, 就认为是重放攻击. 缺点是需要额外保存使用过的随机数, 若记录的时间段较长, 则保存和查询的开销较大.
  2. 加时间戳: 该方法优点是不用额外保存其他信息. 缺点是认证双方需要准确的时间同步, 同步越好, 受攻击的可能性就越小. 但当系统很庞大, 跨越的区域较广时, 要做到精确的时间同步并不是很容易.
  3. 加流水号: 就是双方在报文中添加一个逐步递增的整数, 只要接收到一个不连续的流水号报文(太大或太小), 就认定有重放威胁. 该方法优点是不需要时间同步, 保存的信息量比随机数方式小. 缺点是一旦攻击者对报文解密成功, 就可以获得流水号, 从而每次将流水号递增欺骗认证端.

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

重放攻击防御实践

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

defense

代码如下:

@Resource
private ReactiveStringRedisTemplate reactiveStringRedisTemplate;

private ReactiveValueOperations<String, String> reactiveValueOperations;

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

@Override
protected Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) {
    // 此处的`ATTRIBUTE_OPEN_API_REQUEST_BODY`是前面过滤器存入的
    OpenApiRequest<String> 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() || 
        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
  • 随机数的方式可能造成过多的缓存, 故需要配合时间戳进行过滤, 时间戳不在有效范围内的一律拒绝.

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

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


草堂笺
34 声望3 粉丝