剖析SQLite内存破坏漏洞_集群智慧网络安全云
全国客户服务热线:4006-054-001 疑难解答:159-9855-7370(7X24受理投诉、建议、合作、售前咨询),173-0411-9111(售前),155-4267-2990(售前),座机/传真:0411-83767788(售后),微信咨询:543646
企业服务导航

剖析SQLite内存破坏漏洞

发布日期:2024-05-19 浏览次数: 专利申请、商标注册、软件著作权、资质办理快速响应热线:4006-054-001 微信:15998557370


剖析SQLite内存破坏漏洞

0×00 从黑帽大会说起 前不久,一年一度的黑帽大会于在美国拉斯维加斯举行,安全圈内各位大咖、全球顶级安全企业都使出浑身解数,展示他们最新的研究成果。相信大多数小伙伴都和小编一样,虽然有心想参会,但无奈囊中羞涩。 错过大会怎么办?没关系,我们还可以一起来学习观摩大牛的视频和文档。这次与大家共赏一个国内安全厂商的议题《一石多鸟:利用单个SQLite漏洞破解多个软件》。 0×01 什么是内存破坏漏洞 SQLite是一款轻型数据库,是遵守ACID的关系型数据管理系统,它包含在一个相对小的C库中。作为一款嵌入式数据库,他因占用的资源非常低,数据处理速度快等优点被Andriod、WebKit等流行软件采用。 说起SQLite的内存破坏漏洞,共分为两种类型,一是由SQLite数据库的文件格式引起的内存损坏漏洞,如CVE-2015-7036,CVE-2017-10989,另一个是SQLite解析器触发的sql语句中的错误。CVE-2015-3414,CVE-2015-3415。 提起CVE-2015-7036,fts3_tokenizer是一个绕不开的话题。这是发生在Apple iOS 8.4以前版本和 OS X 10.10.4版本以前的漏洞,原因是内置的SQLite的fts3_tokenizer函数存在任意命令执行漏洞,远程攻击者可以通过SQL命令执行任意指令或导致系统崩溃,拒绝服务。让我们来一起看看这个能让号称最安全的苹果系统都中招的fts3_tokenizer到底是何方神圣。 sqlite中支持fts表(full-text search的简称),fts3其实是sqlite的一个扩展模块,是虚拟表模块,允许用户使用 MATCH ‘keyword’ 查询而非 LIKE ‘%keyword%’ 子串匹配的方式实现全文检索。在实现全文搜索的过程中,对原始内容进行分词是一个必须的过程。SQLite内置的simple和porter分词器只能支持ASCII字符的英文分词,为满足不同语言的需求,SQLite 3.7.13开始引入unicode61分词器以支持unicode,并提供给开发者自行添加分词器的接口。 0×02 fts3_tokenizer的两种烹饪方式 sqlite在fts3_tokenizer.h中提供了各种接口供用户自定义分词器,但其并未提供c函数供用户来注册自定义的分词器,分词器的注册必须使用sql语句来完成。 官方提供了两种fts3_tokenizer函数的使用方式,这两种方式便产生了两种漏洞: 1、SELECT fts3_tokenizer(); 参数中的tokenizer-name是分词器的名称,该用法的返回值是指定名字分词器的sqlite3_tokenizer_module 结构体指针,以 blob 类型表示16进制的一个大端序的内存地址。该用法本来是用来检查分词器是否被注册。但是同时我们也发现,如果是探测一个已经存在的分词器返回值是一个内存地址。在 fts3.c 中可以看到 SQLite3 默认注册了内置分词器 simple 和 porter: if( sqlite3Fts2HashInsert(pHash, "simple", 7, (void *)pSimple)|| sqlite3Fts2HashInsert(pHash, "porter", 7, (void *)pPorter) 以 simple 分词器为例,其注册的指针指向静态区的 simpleTokenizerModule。 static const sqlite3_tokenizer_module simpleTokenizerModule = { 0, simpleCreate, simpleDestroy, simpleOpen, simpleClose, simpleNext,}; 通过获得这个指针,获得 sqlite3 的基地址,根据不同版本调整偏移量,可以计算绕过 ASLR保护机制: SQLite version 3.8.10.2 2015-05-20 18:17:19Enter ".help" for usage hints.Connected to a transient in-memory database.Use ".open FILENAME" to reopen on a persistent database.sqlite> select hex(fts3_tokenizer('simple'));A0CE0D3321560000sqlite> root@kali:/usr/local/bin# grep sqlite /proc/20261/maps555555554000-555555623000 r-xp 00000000 fe:00 3417560 /usr/local/bin/sqlite3555555822000-555555825000 r--p 000ce000 fe:00 3417560 /usr/local/bin/sqlite3555555825000-555555828000 rw-p 000d1000 fe:00 3417560 /usr/local/bin/sqlite3 Offset2lib攻击: 这里提一下关于绕过ASLR保护机制的相关内容: ASLR(Address Space Layout Randomization),地址空间格局的随机化,就是用来防范Ret2libc攻击手段的另一个重要的安全特性。在你知道目标代码或数据定位的前提下,它可以变成一种规避攻击的技术。正因为黑客并不知道整个地址空间的布局,ASLR技术变得极为有效。只有当可执行程序编译为PIE时(地址无关可执行文件),才能最大限度地从ASLR技术那里获得保护,因为其所有组成部分都是从随机地址加载的。 然而,当可执行文件被编译成PIE之后,GNU/Linux下的ASLR实现的过程中,会出现一个名为Offset2lib安全漏洞,其专门用于绕过在GNU/Linux下如ASLR之类的对于普通漏洞的常用防护。 正常情况下,可能需要大概五步进行攻击,攻击的流程总结如下: 提取静态信息 暴力获取saved-IP部分 计算应用基址 计算offset2lib常量 获得内存映射区域 首先,我们的攻击对目标程序和其执行环境做一个离线分析。利用标准的缓冲区溢出漏洞来暴力获取被ASLR隐藏的保存在栈里的应用代码的saved-IP地址(应用地址),这多亏了目标的fork服务器结构。一旦我们获得了目标应用的完整地址,应用的基址就能被计算出来。最后一步则是对整个库做内存映射,这将决定于目标GNU/Linux的版本。获得隐藏的未明信息后,利用ROP应用获得远程shell是非常容易的。 因为fts3_tokenizer好心的提醒了我们基址地址,甚至不需要前三步的计算,通过union或者盲注,我们可以获取到这个基地址信息。 计算出目标库的offset2lib值,它会因系统的不同而不同,但相互之间有很大的相似性。获得这些offset2lib的值有一个迅捷的办法,那就是本地执行该应用,打印出偏移量。offset2lib并不决定于应用本身,我们需要为特定Linux系统版本量身计算。 Distribution Libc ver. Offset2libCentOS 6.5 2.12 0x5b6000Debian 7.1 2.13 0x5ac000Ubuntu 12.04 2.15 0x5e4000Ubuntu 12.10 2.15 0x5e4000 libc(Linux下的ANSI C的函数库。 ANSI C是基本的C语言函数库,包含了C语言最基本的库函数)的基址都可以通过可执行文件基址减去offset2lib值来计算: Libc_base = App_base - offset2lib 获取到libc的内存地址之后的目标就是获取shell了。可以借助ROP(现代栈溢出利用技术基础)来实现,本文就不详细介绍了。 2、SELECT fts3_tokenizer(,); 这里的sqlite3_tokenizer_module ptr表示一个指向sqlite3_tokenizer_module结构的指针并且编码为SQL blob。这种用法用来注册新的分词器,在SQL下执行此形式语句,即可注册一个的分词器。没错,这里就是把指针当成参数直接放进SQL语句中了,这个指针指向一个 sqlite3_tokenizer_module 结构体,前文已经提到其中包含数个回调函数指针,注册完成分词器后,SQLite3 在处理一些 SQL 查询时将会执行分词器的回调函数以获得结果。 攻击者构造出一个结构体之后,获取到该结构体的内存地址,并使用 SQL 注入等手段让目标注册构造好的“分词器”,再通过 SQL 触发特殊回调就可以实现劫持 IP 寄存器,执行任意代码。接下来进一步分析这个攻击面是否可以被利用。 现在来尝试触发 xCreate 回调执行任意代码。在SQLite3 控制台输入如下查询即可导致段错误: SQLite version 3.8.10.2 2015-05-20 18:17:19Enter ".help" for usage hints.Connected to a transient in-memory database.Use ".open FILENAME" to reopen on a persistent database.sqlite> select fts3_tokenizer('simple', x'4141414141414141'); create virtual table a using fts3;AAAAAAAAProgram received signal SIGSEGV, Segmentation fault.0x00005555555a2178 in sqlite3Fts3InitTokenizer (pHash=pHash@entry=0x55555582d7c8, zArg=zArg@entry=0x5555556019cf "simple", ppTok=ppTok@entry=0x7fffffffc7d8, pzErr=pzErr@entry=0x7fffffffc8f8) at sqlite3.c:141967141967 rc = m->xCreate(iArg, aArg, ppTok); 用gdb查看崩溃的上下文: [----------------------------------registers-----------------------------------]rax 0x4141414141414141 4702111234474983745rbx 0x0 0rcx 0x0 0rdx 0x7fffffffc7d8 140737488340952rsi 0x0 0rdi 0x0 0rbp 0x0 0x0rsp 0x7fffffffc6b0 0x7fffffffc6b0r8 0x60 96r9 0x73 115r10 0x555555604aa0 93824992955040r11 0x1 1r12 0x0 0r13 0x55555583f8ee 93824995293422r14 0x7fffffffc6dc 140737488340700r15 0x55555583f8e8 93824995293416rip 0x5555555a2178 0x5555555a2178 eflags 0x10297 [ CF PF AF SF IF RF ]cs 0x33 51ss 0x2b 43ds 0x0 0[-------------------------------------code-------------------------------------]0x00005555555a2173 <+307>: mov %r12,%rsi0x00005555555a2176 <+310>: mov %ebx,%edi=> 0x00005555555a2178 <+312>: callq *0x8(%rax)0x00005555555a217b <+315>: test %eax,%eax0x00005555555a217d <+317>: mov %eax,%ebp0x00005555555a217f <+319>: jne 0x5555555a21d8 0x00005555555a2181 <+321>: mov (%rsp),%rax0x00005555555a2185 <+325>: mov 0x18(%rsp),%rdx0x00005555555a218a <+330>: mov (%rax),%rax0x00005555555a218d <+333>: mov %rdx,(%rax)0x00005555555a2190 <+336>: mov %r12,%rdi rax 注册时提交的指针参数,cast将blob类型数据转换为指针,SQLite 完全没有对指针做任何有效性检查,直接进行了回调的调用。其对应源代码位于 ext/fts3/fts3_tokenizer.c 的 sqlite3Fts3InitTokenizer 函数: m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1); if( !m ){ sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", z); rc = SQLITE_ERROR; }else{ char const **aArg = 0; ... (省略部分代码) rc = m->xCreate(iArg, aArg, ppTok); assert( rc!=SQLITE_OK || *ppTok ); if( rc!=SQLITE_OK ){ sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer"); }else{ 整理一下思路,整个攻击流程应该是这样的: 首先利用fts3_tokenizer的第一种方式,查询到sqlite基地址,注意结果是大端序。根据 select sqlite_version() 函数泄漏的版本信息调整偏移量,执行 PRAGMA soft_heap_limit 语句布置需要 call 的目标指令地址,向一个已知内存地址写入一个函数指针,然后这个地址转换为blob类型,作为fts3_tokenizer 函数的第二个参数,进而注册了一个“分词器”,最后通过创建虚拟表,触发 xCreate 回调函数,导致eip劫持,允许远程攻击者执行任意代码。 0×03 亡羊补牢犹未晚 虽然这不是 SQLite 的漏洞,但滥用这一特性可能导致应用程序产生攻击面。 禁用这一特性可以起到缓解的效果。比如Andriod甚至是SQLite自己都在3.11版本就采用了直接禁用这种方式。 重写函数也不为是一个不错的办法,十分流行的WebKit也曾提供选择禁用Web SQL Database作为本地数据库,它采用的语言就是SQLite,但已经被W3C标准移除了。现在WebKit也重写了函数。 但是只是简单的白名单过滤并不是一个优秀的处理方式,Safari浏览器采用sqlite3_set_authorizer()用来授权SQL语句行为,并通过白名单控制了可以执行的SQL语句,但CVE-2015-3659中明确说了如何绕过白名单,同样执行任意代码,或者导致拒绝服务,系统崩溃。 总结一下修复和处理的方式: 如果用不到全文检索,可通过关闭 SQLITE_ENABLE_FTS3 / SQLITE_ENABLE_FTS4 / SQLITE_ENABLE_FTS5 选项禁用之,或者使用 Amalgamation 版本编译; 如果需要使用 MATCH 检索,但不需要支持多国语言(即内置分词器可以满足要求),找到 ext/fts3/fts3.c 中注释掉如下一行代码关闭此函数: && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, “fts3_tokenizer”)) 使用 SQLite3 的 Authorization Callbacks 设置访问控制 0×04 参考链接 [1] 特性还是漏洞?滥用 SQLite 分词器   https://blog.chaitin.cn/abusing_fts3_tokenizer/ [2]《一石多鸟:利用单一的SQLITE 漏洞攻击大量软件》  https://www.blackhat.com/docs/us-17/wednesday/us-17-Feng-Many-Birds-One-Stone-Exploiting-A-Single-SQLite-Vulnerability-Across-Multiple-Software.pdf [3] SQLite   https://www.sqlite.org/ [4] Offset2lib攻击测试:看我如何全面绕过64位Linux的内核防护   http://www.freebuf.com/articles/system/54616.html *本文作者:杭州美创科技,转载请注明来自 FreeBuf.COM

剖析SQLite内存破坏漏洞