在ClickHouse DBMS中发现7个RCE和DoS漏洞

JFrog安全研究团队不断监控开源项目,以发现新的漏洞或恶意包,并与更广泛的社区共享,以帮助改善他们的整体安全态势。作为这项努力的一部分,该团队最近发现了7种新的安全漏洞在ClickHouse,一个广泛使用的开源数据库管理系统(DBMS),致力于在线分析处理(OLAP)。ClickHouse是Yandex为Yandex开发的。Metrica是一个网络分析工具,通常用于获取用户行为的可视化报告和视频记录,以及跟踪流量来源,以帮助评估在线和离线广告的有效性。JFrog安全团队负责任地披露了这些漏洞,并与ClickHouse的维护者合作验证了修复程序。
这些漏洞需要身份验证,但可以由任何具有读取权限的用户触发。这意味着攻击者必须对特定的ClickHouse服务器目标执行侦察,以获得有效的凭据。任何一组凭据都可以,因为即使是拥有最低权限的用户也可以触发所有漏洞。通过触发这些漏洞,攻击者可以使ClickHouse服务器崩溃,泄漏内存内容,甚至导致远程代码执行(RCE)。
以下是JFrog安全团队发现的7个漏洞:
- cve - 2021 - 43304而且cve - 2021 - 43305- LZ4压缩编解码器存在堆缓冲区溢出漏洞
- cve - 2021 - 42387而且cve - 2021 - 42388- LZ4压缩编解码器存在堆越界读漏洞
- cve - 2021 - 42389-在Delta压缩编解码器中除以零
- cve - 2021 - 42390-在Delta-Double压缩编解码器中除以零
- cve - 2021 - 42391-在Gorilla压缩编解码器中除以0
| CVE ID | 描述 | 的潜在影响 | CVSSv3.1得分 |
| cve - 2021 - 43304 | 解析恶意查询时,LZ4压缩编解码器中的堆缓冲区溢出 | 远端控制设备 | 8.8 |
| cve - 2021 - 43305 | 解析恶意查询时,LZ4压缩编解码器中的堆缓冲区溢出 | 远端控制设备 | 8.8 |
| cve - 2021 - 42387 | 解析恶意查询时,在LZ4压缩编解码器中堆出界读取 | 拒绝服务或信息泄露 | 7.1 |
| cve - 2021 - 42388 | 解析恶意查询时,在LZ4压缩编解码器中堆出界读取 | 拒绝服务或信息泄露 | 7.1 |
| cve - 2021 - 42389 | 在分析恶意查询时,在Delta压缩编解码器中除以零 | 拒绝服务 | 6.5 |
| cve - 2021 - 42390 | 解析恶意查询时,在DeltaDouble压缩编解码器中除以零 | 拒绝服务 | 6.5 |
| cve - 2021 - 42391 | 在解析恶意查询时,在Gorilla压缩编解码器中除以零 | 拒绝服务 | 6.5 |
技术背景
ClickHouse服务器允许用户压缩其查询。方法传递压缩查询减压= 1URL查询字符串参数到它的web界面,如下所示:
cat query.bin | curl - ss——data-binary @ 'http://serverIP:8123/?用户= guest1&password = 1234减压= 1”
其中serverIP是ClickHouse服务器的IP地址,该服务器设置了用户“guest1”和密码“1234”。该用户还可以配置“只读”策略。
查询的内容(query.bin)应该是以下格式:
Struct {uint128_t哈希;//谷歌的CityHash128 uint8_t compress_method;uint32_t size_compressed_without_checksum;//整个结构的长度(以字节为单位)(包括compressed_data内容)减去前16字节哈希字段。uint32_t decompressed_size;//期望的解压缩输出大小char compressed_data[0];//压缩后的数据字节(可变长度)};
客户端将整个结构提供给服务器,从而控制其所有内容。
压缩的数据通过构造CompressedReadBuffer实例,并将结构体作为其输入。
CompressedReadBuffer的代码调用readCompressedData它读取结构并提取其长度值,在结构内容(不包括哈希字段)上计算CityHash128,并根据结构的哈希字段进行验证。然后,它调整大小(基本上realloc ())最初的分配用于保存解压缩数据的内存缓冲区。然后,通过ICompressionCodec::解压缩,选定的编解码器的doDecompressData被称为。
在cve - 2021 - 42387,cve - 2021 - 43304,cve - 2021 - 42388而且cve - 2021 - 43305LZ4编解码器调用LZ4::解压(源,dest, source_size, dest_size, ..)使用“compressed_data”作为源,它的长度为source_size,调整大小的内存缓冲区为dest,结构体的“decompressed_size”值为dest_size。LZ4::decompress最终调用LZ4::decompressImpl(source, dest, dest_size)它在循环中执行实际的LZ4解压缩——以用户控制的长度和偏移量(作为compressed_data字节的一部分提供)将压缩输入的不同部分复制到解压缩的输出内存缓冲区。它定义了用于跟踪源(ip)和dest (op)中的当前位置的指针变量。
CVE-2021-43304 -堆缓冲区溢出漏洞
以下是与CVE-2021-43304相关的LZ4::decompressImpl()代码:
void NO_INLINE decompressImpl(const char * const dest, size_t dest_size){…虽然(正确){……wildCopy(op, ip, copy_end);///这里我们可以在缓冲区后写入copy_amount - 1字节。IP +=长度;Op = copy_end;If (copy_end >= output_end) return;...}}
知识产权是指向压缩缓冲区和的指针吗人事处是指向已分配目标缓冲区的指针,该缓冲区的大小为头文件中传递的给定decompressed_size。copy_end是指向复制区域结束的指针。
copy_amount模板参数,取值为8、16、32。复制区域被复制成块,每个块的大小都是copy_amount.例如,这是wildCopy16的实现:
inline void wildCopy16(UInt8 * dst, const UInt8 * src, const UInt8 * dst_end) {/// Unrolling with clang is doing >10% performance降级。#if defined(__clang__) #pragma nounroll #endif do {copy16(dst, src);DST += 16;SRC += 16;} while (dst < dst_end);}
因为用户控件decompressed_size对于压缩缓冲区,攻击者可以利用这种情况,通过使用包含decompressed_size的头来准备压缩数据,该头小于压缩数据的实际大小。请注意,溢出的长度,以及源的分配大小和溢出字节内容完全由用户控制,这极大地促进了利用。
还要注意,现有的“if (copy_end >= output_end)”大小检查并不能防止此漏洞,因为它在复制操作之后出现。CVE-2021-43305类似于CVE-2021-43304,但是涉及到不同的复制操作(其源是目标缓冲区的受控偏移量)。
利用cve - 2021 - 43304
为了证明CVE-2021-43304的可利用性,我们创建了一个专门制作的压缩文件,并按照前面的解释发送了它。bin文件由以下头文件组成:
- hash =匹配的计算Cityhash
- compress_method = 0x82 (LZ4方法)
- Size_compressed_without_checksum = 0xc80a
- Decompressed_size = 0x1
对于压缩的数据,我们使用了' \xff '(重复200次)' A '(重复5100次)。这些是任意的值。由此产生的畸形压缩文件:00000000 26 fc 61 db c0 83 bb 0a db 58 5a f0 34 e1 30 f6 |&.a......XZ.4.0.|
00000010 82 0a c8 000001 000000 f0 ff ff ff ff ff |................|
00000020 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
000000e0 ff ff 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |.. aaaaaaaaaaaa |
000000f0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
*
0000年c81a
在LZ4::decompressImpl()内部的循环中使用了200个0xff:
void NO_INLINE decompressImpl(const char * const dest, size_t dest_size){…虽然(正确){……size_t长度;Auto continue_read_length = [&] {unsigned s;Do {s = *ip++;长度+= s;} while(不太可能(s == 255));};///获取文本长度。Const unsigned token = *ip++; length = token >> 4; if (length == 0x0F) continue_read_length(); /// Copy literals. UInt8 * copy_end = op + length; ... wildCopy(op, ip, copy_end); /// Here we can write up to copy_amount - 1 bytes after buffer. ... } }
会增加长度通过0xff * 200 = 51000,这正是其余数据的大小。
因此,尽管解压缩的大小为1,但会将更大的大小复制到目标。
通过将查询发送到一个易受攻击的ClickHouse服务器,同时调试服务器的进程,我们设法定期得到以下崩溃,证明了指令指针寄存器的控制,因为代码分支到一个从RAX寄存器中获取的地址,它已经被我们的“a”值覆盖:

虽然这个特定的崩溃是统计数据,但我们相信,通过适当的堆整形技术,可以开发出稳定的漏洞。
CVE-2021-42388和CVE-2021-42387 -堆OOB读取漏洞
在LZ4: decompressImpl ():
void NO_INLINE decompressImpl(const char * const dest, size_t dest_size){…虽然(正确){……const UInt8 * match = op - offset;...if(长度> copy_amount * 2) wildCopy(op + copy_amount, match + copy_amount, copy_end);...}}
作为LZ4::decompressImpl()循环的一部分,从compressed_data中读取一个16位无符号用户提供的值(' offset ')。它从当前操作中减去并存储在匹配指针(人事处指针是否以…开始桌子然后继续前进)。没有人能证实匹配指针不小于桌子.之后,有一个从匹配到输出指针的复制操作——可能复制越界内存从' dest '内存缓冲区之前.访问缓冲区边界之外的内存可能会暴露敏感信息,或者在某些情况下由于分段错误导致应用程序崩溃。
CVE-2021-42387是一个与CVE-2021-42388类似的漏洞,作为复制操作的一部分,它超出了压缩缓冲区(源)的上限。
CVE-2021-42389、CVE-2021-42390和CVE-2021-42391 -除零漏洞
这些是ClickHouse支持的各种编解码器中的零除漏洞。它们基于将压缩缓冲区的第一个字节(在上面的“技术背景”部分中描述)设置为零。解压缩代码读取压缩缓冲区的第一个字节,并对其执行取模操作以获得余数:
UInt8 bytes_size = source[0];UInt8 bytes_to_skip = uncompressed_size % bytes_size;
在Intel x86-64中的大多数情况下,取模操作是由DIV指令执行的,它除了除数外,还将余数保存在寄存器中。因此,如果bytes_size为0,它将以除0结束。
这些漏洞是通过“智能模糊”解压机制发现的。智能模糊利用输入格式的知识来生成(相对地)符合预期协议模式的输入数据,而不是完全随机的数据。
修复和变通方法
为了解决这些问题,更新ClickHouse到v21.10.2.15-stable版本及以上版本。
如果无法升级,请在服务器中添加防火墙规则,仅对特定客户端限制对web端口(8123)和TCP服务器端口(9000)的访问。
JFrog产品易受攻hth华体会最新官方网站击吗?
JFrog产hth华体会最新官方网站品不容易受到这个问题的影响,因为它们不使用ClickHouse DBMS
确认
我们要感谢ClickHouse公司团队迅速而专业地处理了这个问题。
了解更多
除了暴露新的安全漏洞和威胁,JFrog还通过自动安全扫描为开发人员和安全团队提供方便地访问其软件的最新相关信息。探讨JFrog x光可以帮助你。
问题吗?想法吗?与我们联络research@m.si-fil.com有任何疑问。
