如何设计实现一个轻量的开放API网关之重放攻击及防护java
文章地址: blog.piaoruiqing.com/2019/08/11/…react
上一篇文章《开放API网关实践(一)》中的接口设计提到timestamp
和nonce
两个参数的做用是用来防重放. 本文就重放攻击及其防护进行探讨. 先抛出两个问题:api
什么是重放
, 先举个例子:浏览器
打开浏览器的调试工具并访问一个网站, 在网络工具中找到一个请求并右键选择Replay
. 如图:缓存
上述的重放
操做是接口调试中比较经常使用的手段, 这种操做可让咱们跳过认证信息的生成过程, 直接重复发起屡次有效的请求.安全
而重放攻击
是一种黑客经常使用的攻击手段, 又称重播攻击
、回放攻击
, 是指攻击者发送目的主机已接收过的数据
, 以达到欺骗系统的目的, 主要用于身份认证过程, 破坏认证的正确性.服务器
举个易懂的例子:markdown
一样的数据
重复发给服务器就能达到欺骗的目的.序号 | 名称 | 数量 | 备注 |
---|---|---|---|
1 | 服务器 | 2 | 10.33.30.101 - 真实服务器 10.33.30.100 - 伪造服务器 |
2 | 域名 | 1 | replay-test.piaoruiqing.com (10.33.30.101) |
3 | DNS 服务器 |
1 | 用来模拟DNS 劫持 |
DNS配置, 将域名replay-test.piaoruiqing.com
指向内网中服务器的IP. 并启动服务器.网络
使用postman
发起一个正常的请求, 其中签名已在Pre-request-script
中生成.app
修改内网的dnsmasq
配置, 将域名replay-test.piaoruiqing.com
指向伪造的服务器10.33.30.100
.
此时向replay-test.piaoruiqing.com
发起的请求便会被发送到伪造的服务器上(10.33.30.100), 手动将请求的数据保存下来. 因为请求带有签名, 且攻击者并无拿到私钥, 故没法篡改请求, 但能够进行重放攻击. 如图, 伪造服务器已成功接收到请求数据:
使用上一步保存下来的数据, 直接向真实服务器发送请求(带有签名数据). 如图:
事实上, 签名、加密等手段并不能防护重放攻击, 由于攻击者拦截到的数据已经是正确的请求数据, 即便没法破解其内容, 也能够重放向服务器发送原数据以达到欺骗的目的.
加随机数
: 该方法优势是认证双方不须要时间同步,双方记住使用过的随机数, 如发现报文中有之前使用过的随机数, 就认为是重放攻击. 缺点是须要额外保存使用过的随机数, 若记录的时间段较长, 则保存和查询的开销较大.
加时间戳
: 该方法优势是不用额外保存其余信息. 缺点是认证双方须要准确的时间同步, 同步越好, 受攻击的可能性就越小. 但当系统很庞大, 跨越的区域较广时, 要作到精确的时间同步并非很容易.
加流水号
: 就是双方在报文中添加一个逐步递增的整数, 只要接收到一个不连续的流水号报文(太大或过小), 就认定有重放威胁. 该方法优势是不须要时间同步, 保存的信息量比随机数方式小. 缺点是一旦攻击者对报文解密成功, 就能够得到流水号, 从而每次将流水号递增欺骗认证端.
在实际使用中, 常将1和2结合使用, 时间戳有效期内判断随机数是否已存在, 有效期外则直接丢弃.
咱们采起时间戳
+随机数
的方式来实现一个简单的重放攻击拦截器. 时间戳和随机数互补, 既能在时间有效范围内经过校验缓存中的随机数是否存在来分辨是否为重放请求, 也能在缓存失效后(缓存有效时间和时间范围一致)经过时间戳来校验该请求是否为重放. 如图:
代码以下:
@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
重放攻击是一种经常使用且有效的攻击手段, 其危害不可忽视, 尽管能够经过业务层面来保障数据的正确性, 但依旧会给系统形成没必要要开销, 在网关层过滤掉重放请求是一个不错的选择.
若是这篇文章对您有帮助,请点个赞吧 ( ̄▽ ̄)"
欢迎关注公众号: