SQLite是世界上部署最多的软件之一。然而,从安全的角度来看,它只是通过WebSQL和浏览器开发的视角来进行安全检查。对于安全检查来说,这只是冰山一角。
在我们的长期研究中,我们尝试了在SQLite中利用内存损坏问题,而不依赖SQL语言以外的任何环境。使用我们创新的查询劫持和面向查询编程技术,我们证明了能够在SQLite引擎中利用内存损坏问题。我们将在几个实际场景中演示这些技术:pwning密码窃取者后端服务器,以及使用更高的权限实现IOS持久性。
我们希望发布我们的研究方法,能对其他安全从业人员有所启发,继续在无数可用的场景中审查SQLite。鉴于SQLite实际上是嵌入到每个主要的操作系统(桌面或移动),因此审计的机会还是非常多的。此外,这里提供的许多原语并不是SQLite独有的,可以移植到其他SQL引擎。
这项研究开始时,omriher和我正在查看一些由臭名昭著的密码窃取程序泄露的源代码。虽然存在很多密码窃取程序(Azorult、Loki Bot和Pony等),但他们的操作方式基本上是相同的:
计算机感染恶意软件,恶意软件在使用凭据时捕获凭据,或者收集由各种客户端维护的存储凭据。
客户端软件使用SQLite数据库的情况并不少见。
在恶意软件收集到这些SQLite文件后,它会将它们发送到C2服务器,在那里使用PHP对它们进行解析,并将其存储在包含所有被盗凭据的集合数据库中。
审计这些密码窃取程序泄露的源代码,我们开始猜测上面描述的攻击面。
这些攻击会带来很大的影响,因为SQLite是目前部署最广泛的软件之一。
一个令人惊讶的复杂代码库,可在几乎任何设备中使用。这还不值得我们为此研究一番吗?
你可能没有意识到,你现在此刻正在使用SQLite的可能性很高。
SQLite是一个C语言库,它实现了一个 小型, 快速, 自包含, 高可靠性, 功能齐全的 SQL数据库引擎。SQLite是世界上使用最多的数据库引擎。它内置于所有移动电话和大多数计算机中,并且捆绑在人们每天使用的无数其他应用程序中。
与大多数其他SQL数据库不同,SQLite没有单独的服务器进程。SQLite直接读写普通磁盘文件。包含多个表、索引、触发器和视图的完整SQL数据库包含在单个磁盘文件中。
鉴于我们控制数据库及其内容,我们可以将可用的攻击面分为两部分:数据库的加载和初始解析,以及对数据库执行的SELECT查询。
sqlite3_open完成的初始加载实际上是一个非常有限的攻击面, 它基本上是很多用于打开数据库的设置和配置代码;最主要的攻击面是头解析,针对AFL的测试。
当我们开始查询数据库时,事情变得更加有趣。
使用SQLite作者的话:
“SELECT语句是SQL语言中最复杂的命令。
尽管我们无法控制查询本身(因为它在我们的目标中是硬编码的),但仔细研究SELECT过程是一个必不可少的过程。
由于SQLite3是虚拟机,因此必须首先使用一个sqlite3_preare*
例程将每个SQL语句编译成字节码程序。
在其余操作中,prepare函数遍历并扩展所有SELECT子查询。此过程的一部分是验证所有相关对象(如表或视图)是否确实存在,并在主模式中定位它们。
每个SQLite数据库都有一个sqlite_master表,该表定义了数据库及其所有对象(例如表、视图、索引等)的模式。sqlite_master表定义为:
我们特别感兴趣的部分是sql列。
此字段是用于描述对象的DDL(数据定义语言)。
在某种意义上,DDL命令类似于C头文件。DDL命令用于定义数据库中数据容器的结构、名称和类型,就像头文件通常定义类型定义、结构、类和其他数据结构一样。
如果我们检查数据库文件,这些DDL语句实际上以纯文本形式出现:
在查询准备期间,sqlite3LocateTable()尝试查找描述我们感兴趣的查询表的内存结构。
sqlite3LocateTable()读取sqlite_master中可用的模式,如果这是第一次这样做,它还会对每个结果j进行回调,以验证DDL语句是否有效,并构建必要的内部数据结构来描述所讨论的对象。
当我们了解了这个过程后,不禁要问到,我们可以简单地替换文件中以纯文本形式出现的DDL吗?
如果我们可以将我们自己的SQL注入到文件中,也许我们可以影响它的行为。
基于上面的代码片段,DDL语句似乎必须以“create”开头。
检查SQLite的文档发现,我们可以创建以下对象:
CREATE VIEW命令给了我们一个有趣的想法。简单来说,VIEW只是预先打包的SELECT语句。如果我们用兼容的VIEW替换目标软件所期望的表,那么有趣的机会就会显露出来。
想象一下以下场景:
原始数据库有一个名为dummy的表,其定义为:
目标软件使用以下内容对其进行查询:
如果我们将dummy作为view,我们实际上可以劫持这个查询:
这个“陷阱”view使我们能够劫持查询-这意味着我们生成了一个完全由我们控制的全新查询。
这种细微差别极大地扩展了我们的攻击面,从极小的头解析和加载软件执行的无法控制的查询,到现在我们可以通过修补DDL并使用子查询创建我们自己的view来与SQLite解释器的大部分进行交互。
现在我们可以与SQLite解释器进行交互,我们的下一个问题是SQLite中内置了哪些开发原语?它是否允许任何系统命令,从文件系统读取或写入文件系统?
由于我们不是第一个从exploitation角度注意到SQLite的人,所以回顾以前在该领域所做的工作是很有必要的。我们从最基本的开始。
我们希望熟悉SQLite提供的内部原语。有什么系统命令?我们可以加载任意库吗?
看起来最直接的技巧是附加一个新的数据库文件,并使用如下方式向其写入:
我们附加一个新数据库,创建单个表并插入单行文本。然后,新数据库创建一个新文件(因为数据库是SQLite中的文件),其中包含我们的Web shell。
PHP解释器非会解析我们的数据库,直到PHP的开始标记“<?”。
但是,DDL不能以“ATTACH”开头
另一个相关选项是load_extension函数。虽然此函数应该允许我们加载任意共享对象,但默认情况下它是禁用的。
与任何其他用C编写的软件一样,在评估SQLite的安全性时,内存安全问题绝对是需要考虑的问题。
在他伟大的博客文章中,michałzalewski描述了他如何用afl对sqlite进行模糊测试,以获得一些令人印象深刻的结果:在仅仅30分钟的模糊测试中出现了22个bug。
有趣的是,SQLite从此开始使用AFL作为其卓越测试套件中不可或缺的一部分。
这些内存损坏问题都得到了应有的重视,但是,从攻击者的角度来看,如果没有合适的框架来利用这些漏洞,这些漏洞存在与否基本没有任何差别。
现代缓解技术在利用内存损坏问题方面构成了一个主要障碍,攻击者需要找到一个更灵活的环境。
安全研究社区很快就会找到完美的目标!
Web SQL数据库是一种网页API,用于将数据存储在数据库中,可以通过JavaScript使用SQL的变体进行查询。W3C Web应用程序工作组在2010年11月停止了该规范的工作,理由是缺少SQLite以外的独立实现。
目前,该API仍受Google Chrome、Opera和Safari的支持。
他们都使用SQLite作为该API的后端。
在一些最流行的浏览器中,任何网站都可以访问SQLite中不受信任的输入,这引起了安全社区的注意,因此,漏洞的数量开始上升。
突然之间,JavaScript解释器可以利用SQLite中的bug来实现浏览器漏洞利用。
已经发表了几份令人印象深刻的研究报告:
如CVE-2015-7036。
不受信任的指针取消引用fts3_tokenizer()。
Chaitin团队在Blackhat 17中展示的更复杂的利用。
fts3OptimizeFunc()中的类型混淆
Exodus最近利用的麦哲伦漏洞。
fts3SegReaderNext()中的整数溢出
过去WebSQL的研究中表明,名为“FTS”的虚拟表模块可能是我们研究的重点。
全文搜索(Full-text search,FTS)是一个虚拟表模块,允许对一组文档进行文本搜索。
从SQL语句的角度来看,虚拟表对象看起来像任何其他表或视图。但是在幕后背后,虚拟表上的查询调用影子表上的回调方法,而不是通常对数据库文件的读写。
一些虚拟表实现,如FTS,利用真实(非虚拟)数据库表来存储内容。
例如,当将字符串插入到FTS3虚拟表中时,必须生成一些元数据以允许高效的文本搜索。
此元数据最终存储在名为%_Segdir
和%_Segments
的实表中,而内容本身存储在%_content
中,其中%
是原始虚拟表的名称。
这些包含虚拟表数据的辅助实表称为“影子表”
由于它们的信任性质,在影子表之间传递数据的接口为bug提供了肥沃的土壤。CVE-2019-8457-我们在RTREE虚拟表模块中发现的一个新的OOB读取漏洞,很好地证明了这一点。
用于地理索引的RTREE虚拟表应该以整型列开头。因此,其他RTREE接口期望RTREE中的第一列是整数。然而,如果我们创建一个表,其中第一列是字符串,如下图所示,并将其传递给rtreenode()接口,则会发生OOB读取。
现在我们可以使用查询劫持来获得对查询的控制,并且知道在哪里可以找到漏洞,现在是时候利用漏洞了。
以前关于SQLite漏洞利用的文章清楚地表明包装封装环境总是有必要的,无论是这篇关于滥用SQLite令牌生成器的博客文章中看到的PHP解释器,还是使用JavaScript解释器在Web SQL上的相关工作,封装环境总是非常有必要。
很多安全社区很擅长利用JavaScript进行漏洞利用开发,那么我们可以用SQL实现类似的原语吗
考虑到SQL是图灵完备的([1],[2]),基于我们的pwning经验为漏洞利用开发创建一个原始列表。
内存泄漏。
将整数打包和解包到64位指针。
指针算法。
在内存中创建复杂的假对象。
堆喷射
我们将逐一解决这些原语,并使用SQL来实现它们
为了在PHP7上实现RCE,我们将利用尚未被修复的1-day CVE-2015-7036。
为什么一个4年多的bug还未被修复?
只有在允许来自不受信任的源(Web SQL)的任意SQL的程序上下文中,此bug才容易被触发,因此相应地减轻了它的影响。
然而,SQLite的使用是如此多用途,以至于我们实际上仍然可以在许多场景中触发它。
CVE-2015-7036是一个非常好利用的bug。
简而言之,易受攻击的fts3_tokenizer()函数在使用单个参数(如“simple”、“porter”或任何其他已注册的tokenizer)调用时返回tokenizer地址。
当用2个参数调用fts3_tokenizer时,fts3_tokenizer会用第二个参数中的blob提供的地址覆盖第一个参数中的 tokenizer地址。
在重写某个tokenizer之后,使用该tokenizer的FTS表的任何新实例都允许我们劫持程序的流。
我们的漏洞利用计划:
泄漏tokenizer地址。
计算基地址。
伪造一个将执行我们的恶意代码的假tokenizer。
用我们的恶意tokenizer覆盖其中一个tokenizer。
实例化fts3表来触发恶意代码
我们很自豪地提出我们自己独特的方法来利用我们熟悉的结构化查询语言进行漏洞开发。
我们与社区分享QoP,希望鼓励研究人员追求数据库引擎开发的无限可能性。
下面的每个原语都附带一个来自sqlite3 shell的示例。
我们的最终目标是将所有这些原语放置在sqlite_master表中,并劫持加载和查询恶意SQLite db文件的目标软件发出的查询。
诸如ASLR之类的减轻无疑提高了内存破坏利用的门槛。绕过它的一个常见方法是了解我们周围的内存布局。
这被广泛称为内存泄漏。
内存泄漏是它们自己的漏洞子类,每个漏洞的设置都略有不同。
在我们的示例中,泄漏是SQLite返回一个BLOB。
这些blob是一个很好的泄漏目标,因为它们有时包含内存指针
易受攻击的fts3_tokenizer()使用单个参数调用,并返回请求的令牌化器的 tokenizer. hex()被人们读取。
我们显然得到了一些内存地址,但由于小字节序而被反转。
当然,我们可以使用一些SQLite内置的字符串操作来反转它。
substr()似乎非常适合!我们可以读取小字节序BLOB,但这提出了另一个问题:我们如何存储东西?
自然,在SQL中存储数据需要INSERT语句。由于sqlite_master经过了严格的验证,我们不能使用INSERT,因为所有语句都必须以“create”开头。我们应对这个挑战的方法是简单地将我们的查询存储在一个有意义的VIEW下,并将它们链接在一起。
这可能看起来没有什么大不同,但是随着我们的链条变得越来越复杂,能够使用伪变量肯定会让攻击链越来越容易。
如果您曾经做过任何pwning CTF,那么指针的打包和解包的概念应该不陌生。
这个原语应该可以轻松地将十六进制值(就像我们刚刚实现的泄漏)转换为整数。这样做允许我们在接下来的步骤中对这些指针执行各种计算。
此查询使用substr()以反向方式逐个字符迭代十六进制字符串。
使用这个聪明的技巧,只需对基于1的instr()进行少量调整,就可以完成这个字符的转换。
现在所需要的就是*
符号右边的进行适当移位。
指针算术是一项相当容易实现的任务,有整数就可以了。例如,从泄漏的tokenizer指针中提取图像库非常简单,如下所示:
在读取泄漏的指针并按照我们的意愿操作它们之后,将它们打包回它们的little-endian形式是非常有必要的,这样我们就可以在某个地方编写它们。
SQLite char()在这里应该有用,因为它的文档声明它将“返回由具有整数的Unicode码点值的字符组成的字符串”。
在有限范围整数内,char()表现的非常完美。
较大的整数被转换为其2字节的代码点。
在与SQLite文档发生冲突之后,我们突然有了一个奇秒的想法:我们的漏洞实际上是一个数据库
我们可以事先准备一个表,将整数映射到它们的期望值。
现在我们的指针打包查询如下:
编写单个指针肯定有用,但仍然不够。许多内存安全问题利用场景要求攻击者伪造内存中的某些对象或结构,甚至编写ROP链。
我们将对前面介绍的几个构建块进行字符串处理。
例如,让我们伪造我们自己的tokenizer,原因在此。
我们的伪tokenizer应该符合SQLite在这里定义的预期接口:
使用上面描述的方法和一个简单的连接查询,我们能够很容易地伪造所需的对象。
在低级调试器中验证结果时,我们看到确实创建了一个假的tokenizer对象。
现在我们制作了这个伪对象,有时候用它来进行堆喷射非常有用。
不幸的是,SQLite没有像MySQL那样实现REPEAT()函数。
然而,这个线程给了我们一个优雅的解决方案。
zeroblob(N)函数返回一个由N个字节组成的blob,而我们使用replace()将这些零替换为我们的伪对象。
搜索这些0x41表明我们也获得了完美的一致性。注意每0x20字节重复一次。
我们已经知道二值图像的位置,我们能够推断出必要的函数在哪里,并向堆中喷射恶意的tokenizer。
现在是时候用我们的一个喷射的对象覆盖一个tokenizer。然而,由于堆地址也是随机化的,我们不知道我们的喷射被分配到哪里。
堆泄漏要求我们有另一个漏洞。
同样,我们将以虚拟表接口为目标。
由于虚拟表使用底层影子表,所以在不同的SQL接口之间传递原始指针是很常见的。
注意:SQLite 3.20减轻了这种类型的问题。幸运的是,PHP7是用较早的版本编译的。如果是更新版本,这里也可以使用CVE-2019-8457。
要泄漏堆地址,我们需要预先生成一个fts3表,并滥用它的MATCH接口。
正如我们在第一次内存泄漏中看到的那样,指针是小字节序,因此需要反转。幸运的是,我们已经知道如何使用SUBSTR()做到这一点。现在我们知道了堆的位置,并且可以正确地喷射,我们终于可以用我们的恶意tokenizer覆盖一个tokenizer了!
有了所有想要的漏洞利用原语,是时候回到我们开始的地方了:利用密码窃取器C2。
如上所述,我们需要设置一个“陷阱”VIEW来启动我们的攻击。因此,我们需要审计我们的目标,准备正确的VIEW。
如上面的代码片段所示,我们的目标期望我们的数据库有一个名为Notes的表,其中包含一个名为BodyRich的列。为了劫持这个查询,我们创建了以下VIEW.
在查询Notes之后,执行3个QOP链。我们来分析第一个
我们的第一个QOP链应该用大量的恶意tokenizer填充堆。
p64_simple_create、p64_simple_destroy和p64_system本质上都是通过我们的泄漏和打包功能实现的链。
例如,p64_simple_create构造为:
由于这些链变得非常复杂,并且非常重复,我们创建了QOP.py。
通过以pwntools风格生成这些查询,QOP.py使构造链变得更简单。
创建前面的语句变得非常简单,
在iOS上很难实现持久化,因为所有可执行文件都必须作为苹果安全启动的一部分进行签名。幸运的是,SQLite数据库没有签名。
利用我们的新功能,我们将用恶意版本替换其中一个常用的数据库。在设备重新启动并查询恶意数据库之后,我们将获得代码执行。
为了演示这个概念,我们替换了Contacts DB“AddressBook.sqlitedb”。正如在PHP7中利用漏洞一样,我们创建了两个额外的DDL语句。一个DDL语句覆盖默认的 tokenizer“simple”,另一个DDL语句通过尝试实例化被覆盖的 tokenizer来触发崩溃。现在,我们所要做的就是将原始数据库的每个表重写为一个VIEW,该VIEW可以劫持执行的任何查询,并将其重定向到我们的恶意DDL。
将contacts db替换为我们的恶意contacts db并重新启动,会导致以下iOS 崩溃转储:
正如预期的那样,contacts进程在0x4141414141414149处崩溃,在那里可以找到我们的伪造tokenizer的xCreate构造函数。此外,contacts DB实际上在许多进程之间共享。Contacts、Facetime、Springboard、WhatsApp、Telegram和XPCProxy只是查询它的一些进程。其中一些进程比其他进程更有特权。一旦证明我们可以在查询过程的上下文中执行代码,这种技术也允许我们扩展和提升我们的特权。
我们的研究和方法都已负责任地向苹果披露,并被分配了以下CVE:
CVE-2019-8600
CVE-2019-8598
CVE-2019-8602
CVE-2019-8577
考虑到SQLite实际上是几乎所有平台的内置,我认为在挖掘漏洞方面,我们仅仅触及了冰山一角。我们希望安全社区将采取这种创新的研究和发布的工具,并进一步发展它。我认为接下来还可以做以下工作,
挖掘更多的漏洞,这可以通过使用sqlite_version()或sqlite_Compileoption_used()等函数从预制表中选择相关QoP小工具来动态构建漏洞利用。
实现更强的漏洞利用原语,如任意R/W。
寻找查询程序无法验证数据库可靠性的其他场景。
我们确定,简单地查询数据库可能并不像您期望的那样安全。使用我们的查询劫持和面向查询编程的创新技术,我们证明SQLite中的内存损坏问题现在可以可靠地被利用。随着权限层次结构比以往任何时候都更加细分,很明显,我们必须重新考虑受信任/不受信任的SQL输入的边界。为了演示这些概念,我们在运行PHP7的密码窃取器后端上实现了远程代码执行,并在IOS上获得了更高权限的持久性。这只是冰山一角。
原文:https://research.checkpoint.com/select-code_execution-from-using-sqlite/