SQLite攻擊 – iOS持久化控制實現

SQLite是世界上部署最多的軟件之一。但是,SQLite的安全相關測試,目前涉及的基本是網頁應用環境及本地瀏覽器環境。然而上述SQLite的應用場景只是冰山一角,其他大量應用場景及其對應的安全問題同樣值得關注。

在我們的長期研究中,我們嘗試了完全依賴SQL語言環境對內存破壞問題進行利用。使用我們查詢劫持和面向查詢編程的新姿勢,我們實現了在SQLite引擎中可靠地利用內存損壞。下面我們將用幾個真實場景來演示這些技術:用密碼竊取程序攻擊后端服務器、高權限實現iOS持久化控制。

我們希望通過發布研究方法及成果來鼓勵安全研究社區,以便于在各種應用場景中測試SQLite。鑒于SQLite內置于幾乎所有的主流操作系統、桌面系統及移動設備,因此其前景和機會是無限的。此外,本文所提及的許多數據庫原語(primitives)并不是SQLite獨有的,大家可以考慮移植到其他SQL引擎。歡迎大家在自己熟悉的結構化查詢語言中進行新的嘗試和探索。

緣由

這項研究源自于我和@omriher研究一些臭名昭著的密碼竊取程序的部分泄露源碼。雖然有非常多的密碼竊取程序(Azorult,?Loki Bot, 和?Pony?等等),但這些程序的作案手法大致相同:

計算機受到感染,惡意的密碼盜竊軟件要么在憑證使用的時候捕獲憑證,要么就收集各終端上存儲的憑證(存儲在sqlite文件中,客戶端軟件將SQLite數據庫用于憑證存儲的情況并不少見)。

惡意軟件收集到這些SQLite文件后,將它們發送到C2服務器,然后使用PHP進行解析,并將解析出的所有憑證整合到一個集中的數據庫中。

上述攻擊方式是在我們審查這些密碼竊取程序的泄露源碼后推測出的結論。

我們能否利用不可信數據庫的負載和查詢過程來進行反擊呢?如果可以,這將在不計其數的場景中造成重大影響。畢竟官方都宣稱:SQLite是部署最廣泛的軟件之一。

SQLite擁有非常復雜的代碼,幾乎在任何設備上都可以使用。這是我們對于這項研究的動力所在,下面開始切入正題。

SQLite簡介

你此時此刻正在使用SQLite的可能性是很大的,盡管你可能根本沒意識到。

引用作者的話:

“SQLite是一個C語言庫,它實現了一個小型、快速、自包含、高可靠性、功能完整的SQL數據庫引擎。SQLite是世界上使用最多的數據庫引擎。SQLite內置在所有手機和大多數計算機中,并且人們日常使用的很多軟件中還獨立攜帶了SQLite。”

與大多數其他SQL數據庫不同,SQLite沒有單獨的服務進程。SQLite直接對普通磁盤文件進行讀寫操作。一個單獨的文件就可以包括一個完整的,包含多個表,索引和視圖的SQL數據庫。

?攻擊面?

以下代碼片段是密碼竊取程序后端的常見示例:

鑒于我們可以控制數據庫及其內容,因此可以將可用攻擊面分為兩個部分:數據庫的加載和初始解析,以及對其執行的SELECT查詢。

由sqlite3_open完成的數據庫初始加載其實是一個很受限的攻擊面,它基本上只是為了打開數據庫進行大量程序設置和參數配置。所以我們的主要攻擊面是經過AFL模糊測試的頭部解析部分(https://www.sqlite.org/fileformat.html#the_database_header)。

當我們開始對數據庫進行查詢時,就有點意思了。

引用SQLite作者的話:

“SELECT語句是SQL語言中最復雜的命令。”

雖然我們無法控制查詢本身(因為它在我們的目標中是硬編碼的),但對SELECT過程的研究最終被證明是有助于我們后續利用過程的。

因為SQLite3是一個虛擬數據庫,所以每個SQL語句都需要先使用一個sqlite3_prepare*例程編譯成字節碼程序。

此外,prepare函數會遍歷和展開所有SELECT子查詢,并且驗證所有涉及的對象(比如表或視圖)是否存在并在master schema中進行定位。

?sqlite_master 和 DDL?

每一個SQLite數據庫都有一個sqlite_master表,該表定義了數據庫及其所有對象(如表,視圖和索引等)的schema。

sqlite_master的定義如下:

這其中我們感興趣的是sql字段,這個字段是用于描述對象的DDL(數據定義語言)。

從某種意義上來說,DDL類似于C語言中的頭文件,DDL用于定義數據庫中數據容器的結構,名稱和類型,就像C中頭文件通常定義的是類型定義,結構,類和其他數據結構一樣。

如果我們查看數據庫文件,就會看到這些DDL語句以純文本的形式出現:

在查詢準備過程中,sqlite3LocateTable() 會嘗試尋找定義了被查詢的表的內存結構。

sqlite3locateTable()讀取sqlite_master表中可用的schema,如果是首次執行的話,還會對每個結果進行調用,既驗證DDL語句是否有效,并構建必要的用于描述對象的內部數據結構。

DDL修補

了解上述處理過程之后,我們不禁想問,是否可以簡單的替換掉文件中以純文本形式呈現的DDL?如果我們可以向文件注入自己的SQL,那么便可以影響它的行為了。

根據上面的代碼片段,DDL語句似乎必須以“create”開頭。考慮到這種限制,我們需要評估一下我們的攻擊面。

通過檢查SQLite文檔發現,下圖中的這些對象是我們可以創建的。

CREATE VIEW這個命令給了我們一個有趣的想法。簡單來說,VIEW對象只是一個預包裝的SELECT語句。如果我們可以使用兼容的View來替換目標軟件要使用的Table,那么機會便來了。

?劫持任意查詢?

設想如下場景:

原始數據庫中有一個名叫dummy的表,該表的定義如下:

目標軟件使用如下命令對dummy表進行查詢:

如果我們將dummy制作成VIEW的話,我們便可以劫持這個查詢了:

這個視圖讓我們可以劫持查詢——這就意味著我們可以生成一個完全受我們控制的新查詢。

這種細微的差別極大的擴展了我們的攻擊面。加載程序對頭部的簡單解析及不可控查詢的執行,使得我們可以通過修改DDL來與SQLite解析器的各個部分進行交互并創建包含任意子查詢的視圖。

現在我們已經可以與SQLite解釋器進行交互了,那么我們的下一個問題就是:在SQLite中內建了哪些原語?是否支持任意系統命令執行以及對文件系統的讀寫?

由于我們不是第一批從利用的角度關注到SQLite的人,因此回顧該領域先前的成果還是有意義的。So, 從最基礎的部分開始。

?SQL注入?

作為研究人員,我們甚至很難拼寫沒有“i”(injection,注入)的SQL(由于太常見、太基礎),所以從SQL注入似乎是個比較好的入手點。畢竟我們是想熟悉SQLite提供的內部原語,是否有系統命令,是否可以加載任意庫。

最直接的技巧是添加一個新的數據庫文件并執行如下SQL指令:

我們添加一個新的數據庫,創建一個表并向其插入一行文本。新的數據庫創建一個包含webshell代碼的文件(SQLite中,數據庫直接保持為單文件的)。

PHP解釋器會解析數據庫文件直至遇到PHP起始標簽“<?”。

在密碼竊取的場景中,能夠寫入webshell絕對是一種勝利。但是,還記得嗎?DDL是不能以“ATTACH”開頭的(需要以“Create”開頭)。

另一個相關的選項是load_extension函數。雖然該函數允許我們加載任意的共享對象,但在默認情況下該函數是被禁止的。

?SQLite中的內存損壞?

與其他用C語言編寫的軟件一樣,在評估SQLite安全性時,內存安全問題是必須要考慮的。在Michal Zalewski的博文中(blog post),他描述了如何使用AFL對SQLite進行模糊測試并獲得了意想不到的結果:30分鐘的模糊測試就發現了22個bug。

有趣的是,SQLite從此將AFL列入了其測試套裝中。

從各個方面,內存損壞問題得到了足夠的重視(Richard Hip和他的團隊應該得到極大尊重)。但是,從攻擊者的角度來看,如果沒有合適的框架,這些漏洞還是很難利用的。

現代緩解措施是利用內存損壞問題的主要障礙,攻擊者需要找到更靈活的環境。

相信安全研究社區很快就會找到完美的目標!

?Web SQL?

Web SQL數據庫是一個網頁API,用來將數據存儲到數據庫以便通過JavaScript使用SQl的變體進行查詢。在2010年11月,W3C Web應用程序工作組停用了此規范,理由是缺少除SQLite之外的獨立實現。

目前,Google Chrome,Opera和Safari仍然支持該API,都使用SQLite作為該API的后端。

任意網站通過一些流行的瀏覽器可以寫入SQLite,這個問題引起了安全社區的注意,導致漏洞數量上升。

一些非常著名的瀏覽器中的任意網站都可以訪問SQLite中那些不受信任的輸入,該問題引起了安全社區的注意,隨后漏洞數量開始增長。因為JavaScript解釋器可以利用SQLite中的漏洞來實現瀏覽器攻擊。

幾個令人記憶深刻的研究報告如下:

1、容易實現的目標 (CVE-2015-7036)

  • 不可信信指針取消引用fts3_tokenizer()

2、Chaitin team?在黑帽17會議上演示的?更復雜利用

  • fts3OptimizerFunc()中的類型混淆

3、Exodus對最新麥哲倫漏洞的利用

  • fts3SegReaderNext()中的整數溢出
根據過去的WebSQL研究顯示,名為“FTS”的虛擬表模塊可能是我們的研究目標。

FTS

Full-Text Search(FTS,全文搜索)是一個虛擬表模塊,實現對一組文檔進行文本搜索。

從SQL語句的角度來看,虛擬表對象就跟其他表或視圖一樣。但在內部,對虛擬表的查詢會調用影子表上的回調方法,而不是通常情況下對數據庫文件進行讀寫。

一些如FTS的虛擬表實現會使用真實的數據庫表(非虛擬)來存儲內容。

舉個例子,當要在FTS3虛擬表中插入一個字符串時,必須生成一些元數據以便進行有效的文本搜索。這些元數據最終會被存儲在名為“%_segdir”和“%_segments”的真實表中,而字符串本身被存儲在“%_content”表中,這里的%是原始虛擬表的名字。

這些用來輔助存儲虛擬表數據的真實表被稱為“影子表”(shadow tables)。簡單來說影子表就是一個與原表結構一致但是名字不一樣的普通表。

由于影子表之間相互信任,影子表之間傳遞數據的接口為bug的利用提供了條件。我們在RTREE虛擬表模塊中發現的一個OOB(Out-Of-Band)漏洞——CVE-2019-8457,很好的證明了這一點。

用于地理索引的RTREE虛擬表是一個整數列開頭的表。因此,RTREE接口也認定RTREE表中的第一列為整數。但是,如果我們建立一個第一列是字符串的新表,然后將其傳入rtreenode()接口的話,就會發生OOB。

現在我們就可以使用查詢劫持來控制查詢了,我們也知道了在哪里可以找到漏洞,現在就可以對漏洞進行利用了。

深入SQLite內部進行漏洞利用開發

以前有關SQLite漏洞利用開發的文章都明確地表明環境的包裝是必要的。無論是在這篇牛文(https://medium.com/0xcc/bypass-php-safe-mode-by-abusing-sqlite3s-fts-tokenizer-256ee2555607)中描述關于濫用SQLite tokenizer所使用的PHP解釋器,還是最近在Web SQL上進行便捷利用的JavaScript解析器。

但是,由于SQLite幾乎無處不在,如果限制它自身的利用潛力對我們來講太沒挑戰,所以我們開始探索通過SQLite內部機制實現漏洞利用目的。

目前安全社區常用JavaScript來進行漏洞利用,那么我們是否可以使用SQL來實現類似的效果嗎?

考慮到SQL是圖靈完備的,我們開始基于自己的漏洞挖掘經驗整理出用于EXP開發的原始清單。

用純SQL編寫具有以下功能的漏洞EXP:

  • 內存泄漏
  • 將整數打包和解包為64位指針
  • 指針算數
  • 能在內存中創建復雜的假對象
  • 堆噴射
接下來,我們逐一搞定這些原語功能并用SQL實現。

為了在PHP7上實現RCE,我們將利用一個已發布的漏洞:CVE-2015-7036。

等等,為什么一個4年前的漏洞到現在還沒有被修復?這里面其實有一個很有意思的故事,它也是我們的一個論證示例。

因為只有在允許不可信來源的Web SQL時才可被利用,故而被忽略了。

?利用方案?

CVE-2105-7036是一個非常好用的漏洞。

簡而言之,危險函數fts3_tokenizer()的調用使用單個參數(如”simple”、”porter”或者其他注冊的tokenizer)時會返回一個令牌解析器地址(tokenizer address)。

當fts3_tokenizer()函數使用兩個參數時,fts3_tokenizer會用第二個參數覆蓋第一個參數的令牌解析器地址。

當指定令牌解析器地址被覆蓋之后,我們就可以劫持任何使用了該令牌解析器地址的FTS表實例的程序流了。

我們的漏洞利用方案:

  • 泄露令牌解析器地址
  • 基址計算
  • 偽造一個假令牌指向我們的惡意代碼
  • 使用惡意令牌覆蓋原有令牌
  • 實例化fts3虛擬表以觸發惡意代碼
接著再回到我們的EXP開發上來。

?面向查詢編程(QOP)

我們很自豪地介紹一種使用結構化查詢語言進行漏洞利用開發的獨特方法,我們向社區分享QOP,是希望能以此鼓勵研究人員去追求在數據庫利用中的無限可能。

下面的每個原語都會附帶一個sqlite3命令示例。

別忘了,我們的目標是向sqlite_master表中植入我們的功能原語并在當目標軟件查詢我們的惡意SQLite文件時,劫持目標軟件的查詢。

?內存泄漏——二進制?

諸如ASLR之類的緩解措施無疑提高了內存破壞利用的門檻,對抗ASLR的一種常見辦法是去學習和了解內存布局。這就是眾所周知的內存泄漏。

內存泄漏有一些漏洞子類,每個漏洞也是略有不同。

在我們的例子中,SQLite內存泄漏的類型是BLOB類型(Binary Large Object,二進制大對象)。這些BLOB是一個很好的泄漏目標,因為它們有時包含內存指針。

使用一個參數來調用脆弱的fts_tokenizer()函數,隨后該函數返回請求的tokenizer的內存地址,hex()可以將該地址以16進制顯示。

顯然,我們獲得了內存地址,但是被反轉成小端(little-endianity)了。

當然,我們可以使用一些SQLite內置的字符串操作來反轉地址,substr()就非常適合。

現在我們可以讀取BLOB了,但又出現了另一個問題,我們該如何進行存儲呢?

?QOP鏈?

當然,用SQL存儲數據要求使用INSERT語句,由于sqlite_master有嚴格的驗證機制,所有的語句必須以“CREATE”開頭,所以我們無法使用INSERT。應對這一挑戰的方法就是簡單的將視圖下的各個查詢連接起來。

舉個例子:

這看起來貌似變化不是很大,但是隨著我們的鏈變得越來越復雜,使用偽變量會更簡單。

?對64-bit指針進行解包?

如果你曾經做過pwn挑戰,那么指針解包和打包的概念應該不陌生。

通過該原語(指針解包)可以將十六進制(比如我們剛剛得到的內存泄漏地址)轉化成整數。然后我們便能在接下來的步驟中對這些指針進行各種計算。

如上圖所示的查詢,使用substr()迭代十六進制串的每一位。再使用instr()確定數值并結合移位算法技巧(https://gist.github.com/ChiChou/97a53caa2c0b49c1991e)來實現將字符的轉換為數值。

現在我們需要的就是對“*”后面進行正確的移位。

?指針運算?

當指針是整數形式的時候,進行運算是一個很簡單的任務。例如,從上文中得到的令牌指針中提取image base:

?打包64位指針?

在讀取泄漏的指針并進行處理后,需要將他們打包回原有的小端(little-endian)形式并存儲起來。

安裝SQLite文檔資料, char()函數可以返回一個由具有整型Unicode編碼值的字符組成的字符串。

但事實證明,該函數在有限的整數范圍內情況良好,較大的整數會被轉換成2字節的編碼:

在SQLite官方文檔上碰壁后,我們突然意識到:我們的EXP本身就是一個數據庫,我們可以預先準備一個將整數映射到其預期值的表。

現在我們的指針打包查詢就是:

?在內存中創建復雜的假對象?

編寫一個指針是有用的,但這還不夠。在很多利用內存安全問題進行攻擊的場景中都要求攻擊者在內存中偽造一些對象或者結構,甚至需要編寫ROP鏈。

現在,我們將前文介紹的幾個模塊整合起來。例如,構造我們自己的tokenizer(https://www.sqlite.org/fts3.html#custom_application_defined_tokenizers)。我們偽造的tokenizer需要符合SQLite的接口:

使用上述方法和簡單JOIN查詢,我們就可以輕松的偽造所需的對象了。

通過底層調試器的驗證,可以看到我們的確成功創建了一個偽造的Tokenizer對象。

?堆噴射?

現在我們已經構造了偽對象,有時候用偽造對象是有助于堆噴射的。然而不幸的是,SQLite并沒有像MySQL一樣的REPEAT()函數。

但是,這里(https://stackoverflow.com/questions/11568496/how-to-emulate-repeat-in-sqlite)給出了一個非常秀的解決方案。

zeroblob(N)?函數會返回大小為N字節的BLOB類型,同時使用replace()將其0值替換為我們偽造的對象。

對0x41的搜索結果也顯示我們成功了。注意每0x20字節重復一個結果:

?內存泄漏——堆?

從我們的開發策略來看,似乎我們的方向是正確的。我們已經知道二進制圖像的位置,也能夠推測出必要函數,并且向堆中噴射我們的惡意tokenizer。

現在是時候用我們噴射的對象覆蓋tokenizer了。但是,由于堆的地址是隨機的,我們不知道我們要對那個位置進行噴射。這就需要另一個漏洞來實現堆泄漏。

我們再一次將虛擬表接口作為目標·。

由于虛擬表會使用底層的影子表,因此在不同的SQL接口之間傳遞原始的指針是很常見的。

?注意??在SQLite 3.20中已有相應的措施來緩解這一問題。幸運的是,PHP7使用的是早期版本來進行編譯,如果版本更新了的話,也可以使用CVE-2019-8457漏洞。

要使堆的地址泄漏,我們預先創建一個fts3表使用它的MATCH接口。

正如我們之前在內存泄漏中見過的一樣,該指針是個小端值,所以需要對它進行反轉,當然,我們已經知道如何使用substr()函數來實現這一點。

如此,我們便知道了堆的地址,并且可以進行正確的堆噴射,最終,我們實現了用自己的惡意tokenizer覆蓋原有的tokenizer。

整合

終于,前文期待的所有原語都已經有了,現在回到我們最開始:攻擊密碼竊取器的CC服務器。

如上所述,我們需要設置一個“陷阱”視圖來啟動我們的攻擊。因此,我們需要審查我們的攻擊目標,并為之準備相應的視圖。

上圖中的代碼片段顯示,我們的目標希望收集到的db擁有名為Notes表且含有BodyRich列。為了劫持這個查詢,創建如下視圖:

查詢Notes時會執行3個QOP鏈。先分析一下第一個QOP鏈。

?heap_spray?

該QOP鏈會使用大量的惡意tokenizer對堆進行填充。

p64_simple-create、p64_simple_destrory和p64_system本質上是通過泄露和包裝功能實現的工具鏈。

舉個例子,p64_simple_destory的構造如下:

由于這些鏈都很復雜并且很重復,我們創建了QOP.py來執行。QOP.py以pwntools的形式使得創建如上所述的代碼變得非常簡單:

?Demo?

https://youtu.be/cPfYoxLOi1M (視頻)

?COMMIT?

既然我們已經建立了一個框架來利用其他攻擊者不確定數據庫是否惡意的情況,那么讓我們再來看一下SQLite漏洞利用開發的另一個例子。

iOS 持久性

因為iOS系統中所有的可執行文件都必須作為蘋果安全引導(Secure Boot)的一部分進行簽名,故而一般情況下很難在iOS上實現持久化。然而幸運的是,SQLite數據庫不需要簽名。

利用我們的新功能,我們用惡意數據庫替換一個常用的數據庫。在設備重新啟動之后,當我們的數據庫被查詢時,我們便獲取到了代碼執行權限。

為了進行演示說明,我們替換掉了通訊錄數據庫“AddressBook.sqlitedb”。正如我們在PHP7利用中做的一樣,我們創建兩個額外的DDL語句。一個用于覆蓋默認的tokenizer “simple”,另外一個DDL通過嘗試實例化被覆蓋的tokenizer從而觸發崩潰。現在要做的就是將原始數據庫的每個表重寫為對應的視圖。該視圖將劫持所有的執行的查詢重定向到惡意的DDL。

使用惡意數據庫替換掉contacts DB并重啟,可以看到如下系統崩潰信息:

不出所料,Contacts進程嘗試在0x4141414141414149處查找我們偽造tokenizer的xCreate構造器時崩潰。

除此之外,聯系人數據庫實際上在許多進程之間是共享的。Contacts,Facetime,Springboard,WhatsApp,Telegram和XPCProxy只是處理對聯系人數據庫查詢的進程中的一部分。在眾多的進程中,個別進程擁有跟高級的特權,所以一點證明了可以再查詢過程中執行代碼,那么也就可以用這個方法進行擴展和權限提升。

我們的研究以及方法已經報告給了蘋果,下面是我們的CVE:

  • CVE-2019-8600
  • CVE-2019-8598
  • CVE-2019-8602
  • CVE-2019-8577
接下來的工作

鑒于SQLite實際上幾乎在所有平臺上都內置的,所以我們認為,相對于SQLite的漏洞利用潛力來講,我們所做的只不過是冰山一角罷了。我們希望安全研究社區可以采用我們的研究及其工具,并進一步的來完善它們。在這里我們給出幾個建議:

  • 開發更強大的漏洞利用工具。可以通過使用sqlite_version()或sqlite_compileoption_used()等函數從預制表格中選擇相關的QOP工具來動態構建利用。
  • 實現更強大的原語,比如任意讀寫。
  • 尋找更多“查詢者無法驗證數據庫是否可信”的場景。
結論

我們確認,對數據庫進行簡單的查詢也許并不像你想的那么安全。利用我們的查詢劫持以及QOP技術充分證明了在SQLite中對內存損壞問題可以被可靠利用。隨著權限層次結構變得比以往更加的細分,很明顯我們必須重新考慮受信任與不受信任的SQL輸入的邊界在哪里。為了演示這些概念,我們在一個運行著PHP7的密碼竊取程序后端實現了遠程代碼執行,并在iOS上獲得了更高權限的持久化。我們相信,這些只是SQLite諸多利用中屈指可數的例子。

Check Point公司的IPS可以防范這種威脅:SQLite fts3_tokenizer Untrusted Pointer Remote Code Execution (CVE-2019-8602)。

【via@蘇州極光無限信息技術有限公司