lucywang 二进制安全 2019年9月15日发布
收藏
前言
SQLite是世界上部署最多的软件之一。但是,从安全角度来看,它只是通过WebSQL和浏览器开发的视角进行了安全检查。
在研究人员的长期研究中,曾尝试在SQLite中利用内存损坏漏洞,而不依赖于SQL语言之外的任何环境。使用研究人员的查询劫持和面向查询编程的创新技术,就可以地利用SQLite引擎中的内存损坏漏洞。
鉴于SQLite几乎内置于每个主要的操作系统、桌面或移动设备,研究人员希望通过发布研究人员的安全研究和方法,来避免大规模攻击事件的发生。此外,本文介绍的许多原语并不是SQLite独有的,可以拷贝到其他SQL引擎。
SQLite的攻击面
下面的代码片段是密码窃取器后端的一个相当常见的示例:
由于研究人员控制了数据库及其内容,因此可以将可用的攻击面分为两部分:数据库的加载和初始解析,以及对其执行的SELECT查询。
sqlite3_open完成的初始加载实际上非常有限,它基本上是打开数据库的大量设置和配置代码。
随着研究人员开始查询数据库,事情变得更加有趣。
用SQLite开发者的话来说,就是“SELECT语句是SQL语言中最复杂的命令” 。
虽然研究人员无法控制查询本身(因为它在研究人员的目标中是硬编码的),但仔细研究SELECT过程将对研究人员的探索非常有益。
由于SQLite3是一个虚拟机,因此必须首先使用sqlite3_prepare *例程之一将每个SQL语句编译为字节码程序。
在其他操作中,prepare函数会遍历并扩展所有SELECT子查询。这个过程的一部分是验证所有相关对象(如表或视图)是否确实存在,并将它们定位在主模式中。
sqlite_master和DDL
每个SQLite数据库都有一个sqlite_master表,它定义了所有数据库及其所有对象(例如表、视图、索引等)的模式。
sqlite_master表定义为:
研究人员特别感兴趣的部分是sql列,该字段是用于描述对象的DDL(数据定义语言)。
从某种意义上说,DDL命令类似于C标头文件。 DDL命令用于定义数据库中数据容器的结构、名称和类型,就像标头文件通常定义类型定义、结构、类和其他数据结构一样。
如果研究人员检查数据库文件,这些DDL语句实际上会以纯文本形式出现:
在查询准备过程中,sqlite3LocateTable()试图找到描述研究人员感兴趣的查询的内存结构。
sqlite3LocateTable()读取sqlite_master中可用的模式,如果这是第一次这样做,它还会对每个结果进行回调,以验证DDL语句是否有效,并构建必要的内部数据结构来描述所讨论的对象。
DDL补丁
了解了这个准备过程后,研究人员是否可以简单地替换文件中以纯文本形式出现的DDL ?如果研究人员能将自己的SQL注入到文件中,也许就能影响它的行为。
基于上面的代码片段,DDL语句似乎必须以“create”开头。
考虑到这种限制,研究人员需要评估研究人员发现的攻击面。
在检查SQLite的文档后发现,以下这些可能是研究人员可以创建的对象:
CREATE VIEW命令给研究人员提供一个有趣的想法,简单来说,VIEW只是预先打包的SELECT语句。如果研究人员用兼容的VIEW替换目标软件所期望的表,那么攻击机会就会出现。
劫持查询
设想一下以下场景:假设原始数据库有一个名为dummy的TABLE,定义如下:
目标软件使用以下内容查询它:
如果研究人员将dummy设计为VIEW,研究人员实际上可以劫持此查询:
以下这个VIEW使研究人员能够劫持查询,这意味着可以生成一个研究人员完全控制的全新查询。
既然研究人员可以与SQLite解释器进行交互,接下来的问题是SQLite内置了哪些开发原语?它是否允许任何系统命令从文件系统读取或写入文件系统?
SQL注入
作为研究人员,研究人员很难在没有“i”的情况下拼写SQL,所以它似乎是一个合理的起点。毕竟,研究人员希望熟悉SQLite提供的内部原语。似乎最直接的技巧是附加一个新的数据库文件,并使用以下内容写入:
可以看出,研究人员附加了一个新数据库,创建一个表并插入一行文本。然后,新数据库创建一个包含web shell的新文件(因为数据库是SQLite中的文件)。
PHP解释器非常宽容的性质解析研究人员的数据库,直到它到达PHP开放标记“<?”。
在这个密码窃取者场景中,通过编写一个webshell,研究人员实现了攻击目标,但是请注意, DDL不能以“ATTACH”开头。
另一个相关选项是load_extension函数,虽然此函数可以允许研究人员加载任意共享对象,但默认情况下它是禁用的。
SQLite中的内存损坏漏洞
与使用C语言编写的任何其他软件一样,在评估SQLite的安全性时,内存安全漏洞绝对值得考虑。但是,从攻击者的角度来看,如果没有合适的框架来利用这些漏洞,这些漏洞将成为摆设,无法被利用。
现代缓解措施是利用内存损坏漏洞的主要障碍,攻击者需要找到更灵活的运行环境和使用办法才可以。
Web SQL
Web SQL数据库是一种网页API,用于在数据库中存储数据,可以使用SQL的变体通过JavaScript查询.目前W3C Web应用程序工作组在2010年11月停止了该规范的工作,理由是缺少SQLite以外的独立实现环境。
目前,谷歌Chrome,Opera和Safari仍然支持API。所有这些都使用SQLite作为此API的后端。
在一些最流行的浏览器中,任何网站都可以访问SQLite中不受信任的输入,这引起了攻击者的注意,因此,漏洞的数量开始上升。
在WebSQL研究中,研究人员发现,一个名为“FTS”的虚拟表模块可能是研究人员研究的一个有趣的目标。
FTS
全文搜索(FTS)是一个虚拟表模块,允许对一组文档进行文本搜索。从SQL语句的角度来看,虚拟表对象看起来与任何其他表或视图一样。但在后端,虚拟表上的查询会调用影子表上的回调方法,而不是通常对数据库文件进行读写。
一些虚拟表实现,比如FTS,使用真实的(非虚拟的)数据库表来存储内容。例如,当将字符串插入FTS3虚拟表时,必须生成一些元数据以允许有效的文本搜索。例如,当将字符串插入FTS3虚拟表时,必须生成一些元数据,以便进行有效的文本搜索。这个元数据最终存储在名为“%_segdir”和“%_segment”的实际表中,而内容本身存储在“% _content”中,其中“%”是原始虚拟表的名称。
这些包含虚拟表数据的辅助实际表被称为“影子表”:
由于其可靠性,在影子表之间传递数据的接口就为提供了肥沃的土壤。研究人员在RTREE虚拟表模块中发现的一个新的OOB读漏洞(CVE-2019-8457)很好地说明了这一点。
用于地理索引的RTREE虚拟表预计以整数列开头,因此,其他RTREE接口期望RTREE中的第一列是整数。但是,如果研究人员创建一个表,其中第一列是一个字符串,如下图所示,并将其传递rtreenode()接口,就会发生OOB读取。
现在研究人员可以使用查询劫持来获取对查询的控制,并知道在哪里可以找到漏洞,接下来就可以继续利用开发了。
SQLite内部结构
纯粹用SQL编写的现代漏洞利用具有以下功能:
1. 内存泄漏;
2. 将整数封装并解压缩为64位指;
3. 指针算法;
4. 在内存中创建复杂的伪装对象;
5. Heap Spray(堆喷射)。
研究人员将逐一使用SQL来实现它们,另外,为了在PHP7上实现RCE,研究人员将使用CVE-2015-7036漏洞。
理论上,该漏洞只有在允许来自不可信源(Web SQL)的任意SQL的运行环境中才能发挥攻击威力,但是,SQLite实际上仍然可以在许多情况下被触发。
漏洞利用计划
CVE-2015-7036是一个非常方便的漏洞,简单地说,易受攻击的fts3_tokenizer()函数在使用单个参数(如“simple”,“porter”或任何其他已注册的tokenizer)调用时返回tokenizer地址。
当使用2个参数调用时,fts3_tokenizer将使用第二个参数中blob提供的地址覆盖第一个参数中的tokenizer地址。
在覆盖某个tokenizer之后,使用此tokenizer的fts表的任何新实例都允许研究人员劫持程序的进程。
漏洞利用计划总共分5步:
1. 泄漏tokenizer地址;
2. 计算基本地址;
3. 伪造将执行研究人员的恶意代码的tokenizer;
4. 用研究人员的恶意tokenizer覆盖其中一个tokenizer;
5. 实例化fts3表以触发研究人员的恶意代码。
内存泄漏:二进制
像ASLR这样的缓解措施无疑提高了内存攻击漏洞的利用难度,为此,常见方法是了解内存布局。
在本文的示例中,泄漏就是利用了SQLite返回BLOB。
这些BLOB有一个很好的泄漏目标,因为它们有时会包含内存指针。
使用单个参数调用易受攻击的fts3_tokenizer(),并返回请求的tokenizer的内存地址,hex()可以人类可以读取。研究人员显然得到了一些内存地址,但由于LITTLE-ENDIAN(小字节序、低字节序)而被反转。当然,研究人员可以使用一些SQLite内置字符串操作来再次反转。
QOP链
当然,在SQL中存储数据需要INSERT语句。由于sqlite_master经过了严格的验证,研究人员不能使用INSERT,因为所有语句都必须以“CREATE”开头。研究人员应对这一挑战的方法是将查询存储在一个有意义的视图中,并将它们链接在一起。
这看起来可能没什么大的区别,但是随着我们的链变得越来越复杂,能够使用伪装的变量肯定会让研究人员的攻击更轻松。
解压缩64位指针
这个原语应该可以很容易地将研究人员的十六进制值(比如研究人员刚刚实现的泄漏)转换为整数,这允许研究人员在接下来的步骤中对这些指针执行各种计算。
指针算法
指针算法是一个相当容易的任务,例如,从研究人员泄露的tokenizer指针中提取映像库就像以下这样简单:
封装64位指针
事实证明它运行得相当好,但仅限于有限范围的整数。
较大的整数被转换为它们的2字节代码点,在与SQLite文档发生冲突之后,研究人员突然发现,漏洞实际上是一个数据库。
现在研究人员的指针封装查询如下:
在内存中制作复杂的伪造对象
编写一个指针肯定很有用,但仍然不够,许多内存安全问题利用场景要求攻击者在内存中伪造一些对象或结构,甚至编写一个ROP链。
堆喷射
不幸的是,SQLite没有像MySQL那样实现REPEAT()函数。
但是,这个线程给了研究人员一个解决方案。
zeroblob(N)函数返回一个由N个字节组成的BLOB,而研究人员使用replace()将这些零替换为研究人员的伪造对象。
内存泄漏:堆
此时,研究人员已经知道二进制映像的位置,且能够推断出必要函数的位置,并使用研究人员的恶意tokenizer来进行堆喷射。
现在是时候用研究人员的一个喷射对象覆盖tokenizer了,但是,由于堆地址也是随机的,研究人员不知道喷射的位置。
此时,研究人员将以虚拟表接口为目标。
由于虚拟表使用底层影子表,因此在不同的SQL接口之间传递原始指针是很常见的。
要泄漏堆地址,研究人员需要事先生成一个fts3表并滥用它的MATCH接口。
现在研究人员知道了研究人员的堆位置,并且可以正确喷射!
利用密码窃取器C2
如上所述,研究人员需要设置一个“陷阱”识图来启动漏洞。因此需要检查目标并准备正确的识图。
正如上面的代码片段所示,需要的数据库有一个名为Notes的表,其中包含一个名为BodyRich的列。为了劫持这个查询,研究人员创建了以下视图:
查询Notes后,执行3个QOP链。
heap_spray
第一个QOP链应该使用大量的tokenizer来填充堆。
p64_simple_create,p64_simple_destroy和p64_system基本上都是通过研究人员的泄漏和打包功能实现的所有链。例如,p64_simple_create构造如下:
由于这些链变得非常复杂,非常快,并且非常重复,因此研究人员创建了QOP.py。
QOP.py通过以pwntools样式生成这些查询,使事情变得更简单。
这样,创建以前的语句就变得如此简单:
攻击的持久性
在iOS上很难实现攻击的持久性,因为所有可执行文件都必须作为Apple安全启动的一部分进行签名。幸运的是,SQLite数据库没有签名。利用开发的新功能,研究人员将使用恶意版本替换其中一个常用数据库。这样在重新启动并查询到恶意数据库后,攻击继续。
为了演示这个概念,研究人员替换了Contacts DB“AddressBook.sqlitedb”。正如在研究人员的PHP7漏洞中所做的那样,他们创建了两个额外的DDL语句。一个DDL语句覆盖默认的tokenizer“simple”,另一个DDL语句试图实例化被覆盖的tokenizer,从而触发崩溃。现在,所要做的就是将原始数据库的每个表重写为一个视图,该视图会劫持所执行的任何查询并将其重定向到恶意的DDL。
将contacts db替换为研究人员的恶意contacts db并重新引导,会导致以下iOS crashdump:
与预期一样,contacts进程在0x4141414141414149崩溃,它希望在这里找到研究人员伪造的tokenizer的xCreate构造函数。
此外,contacts db实际上在许多进程之间共享。 Contacts,Facetime,Springboard,WhatsApp,Telegram和XPCProxy只是查询它的一些进程。其中一些进程比其他进程享有更多特权。一旦证明可以在查询过程的上下文中执行代码,这种技术还允许研究人员扩展和提升特权。目前相关的漏洞有:
CVE-2019-8600
CVE-2019-8598
CVE-2019-8602
CVE-2019-8577