使用惡意SQLite資料庫獲取代碼執行
前言
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 db實際上在許多進程之間共享。Contacts,Facetime,Springboard,WhatsApp,Telegram和XPCProxy只是查詢它的一些進程。其中一些進程比其他進程享有更多特權。一旦證明可以在查詢過程的上下文中執行代碼,這種技術還允許研究人員擴展和提升特權。目前相關的漏洞有:
CVE-2019-8600
CVE-2019-8598
CVE-2019-8602
CVE-2019-8577