作者:LoRexxar'@知道創宇404實驗室
時間:2020年10月30日

前言

前段時間開源新版本KunLun-M的時候,寫了一篇《從0開始聊聊自動化靜態代碼審計工具》的文章,里面分享了許多在這些年白盒靜態掃描演變過程中出現的掃描思路、技術等等。在文章中我用了一個簡單的例子描述了一下基于.QL的掃描思路,但實際在這個領域我可能只見過一個活的SemmleQL(也就是CodeQL的原型)。這篇文章中我也聊一聊這相關的東西,也分享一些我嘗試探索的一些全新的靜態掃描方案。

本文提到的小demo phpunserializechain作為星鏈計劃的一員開源,希望能給相關的安全從業者帶來幫助。

本文會提及大量的名詞,其中如有解釋錯誤或使用不當歡迎指正。

什么是.QL?

QL全稱Query Language,是一種用于從數據庫查詢數據的語言。我們常見的SQL就是QL的一種,這是一個很常見的概念。

而.QL是什么呢?Wiki上的解釋是,一種面向對象的查詢語言,用于從關系數據庫中檢索數據。

而.QL又和靜態分析有什么關系呢?我們需要理解一個概念叫做SCID。

SCID: Source Code in Database 是指一種將代碼語法解析并儲存進代碼中的操作方法。而這種數據庫我們可以簡單的稱之為CodeDB。

當我們通過一種方案生成了CodeDB之后,我們就需要構造一種QL語言來處理它。當然CodeQL正是一種實現了CodeDB并設計好了相應的QL語言的平臺。而Semmle QL設計的查詢語言就是一種.QL,它同時符合了幾種特點其中包括SQL、Datalog、Eindhoven Quantifier Notation、Classes are Predicates其中涵蓋了針對代碼的不同邏輯而使用的多種解決方案。當然,本文并不是要討論CodeQL,所以這里我們并不深入解釋Semmle QL中的解決方案。

.QL的概念最早在2007年被提出,詳情可以參考:

為什么使用.QL呢?

在《從0開始聊聊自動化靜態代碼審計工具》中我曾經把基于.QL的認為是未來白盒發展的主要趨勢,其主要原因在于現代普遍使用的白盒核心技術存在許多的無解問題,在上一篇文章中,我主要用一些基于技術原理的角度解釋了幾種現代的掃描方案,今天我就從技術本身聊聊這其中的區別。

其實我在前文中提到的兩種分析方式,無論是基于AST的分析、還是基于IR/CFG的分析方式,他們的區別只是技術基礎不同,但分析的理論差異不大,我們可以粗略的將它們統一叫做Data-flow analysis,也就是數據流分析(污點分析可以算作是數據流分析的變種)。

數據流分析有很多種種類,其本質是流敏感的,且通常來說是路徑不敏感的。當然,這并不是絕對的,我們可以按照敏感類型將其分類:

  • 流敏感分析:flow-sensitive,考慮語句的執行先后順序,這種分析通常依賴CFG控制流圖。
  • 路徑敏感分析:path-sensitive,不僅考慮語句的執行順序,還要分析路徑的執行條件(比如if條件等),以確定是否存在可實際運行的執行路徑。
  • 上下文敏感分析:context-sensitive,屬于一種過程間分析,在分析函數調用目標時會考慮調用上下文。主要面向的的場景為同一個函數/方法在不同次調用/不同位置調用時上下文不同的情況。

當然,需要注意的是,這里僅指的是數據流分析的分類方式,與基于的技術原理無關,如果你愿意,你當然也可以基于AST來完成流敏感的分析工具。

在基于數據流的掃描方案中,如果能夠完整的支持各種語法充足的分析邏輯,我們就可以針對每一種漏洞分析相應的數據流挖掘漏洞。可惜事實是,問題比想象的還要多。這里我舉幾個可能被解決、也可能被暫時解決、也可能沒人能解決的問題作為例子。

1、如何判斷全局過濾方案?
2、如何處理專用的過濾函數未完全過濾的情況?
3、如何審計深度重構的框架?
4、如何掃描儲存型xss?
5、如何掃描二次注入?
6、如何掃描eval中出現的偽代碼邏輯?

現代掃描方案不斷進步的同時,或許許多問題都得到一定程度的解決,但可惜的是,這就像是掃描方案與開發人員的博弈一樣,我們永遠致力于降低誤報率、漏報率卻不能真正的解決,這樣一來好像問題就變得又無解了起來……

當然,.QL的概念的掃描方案并不是為了解決這些問題而誕生的,可幸運的是, 從我的視角來看,基于.QL概念的掃描方案將靜態掃描走到了新的路中,讓我們不再拘泥于探討如何處理流敏感、約束條件等等。

基于.QL的掃描方案,將引擎的實現和規則開發、使用者分割開來,流分析等數據流相關的分析由引擎以及引擎的開發者來完成,使用者只需要關注規則的編寫即可,當然,如何通過定義“謂詞”來編寫高級規則又或是不斷通過多種高級規則來完善規則庫體系,才是基于.QL的掃描方案真正的使用姿勢。

值得注意的是,我們很難在開發層面就區分出基于.QL的掃描方案以及現在普遍的分析方案的區別,我們同樣需要關注代碼流敏感又或是各種限制條件,所謂的CodeDB也只是一種技術手段,使用CodeDB也并不等價于基于.QL的掃描方案。換言之,可能源傘等著名的白盒掃描器中,將多種語言生成的IR統一分析,何嘗不是另一種Code DB呢?

事實也證明,與其說.QL改進了現代諸多白盒分析方案,不如說在當年白盒面臨發展的關口時,大部分人選擇了走向以數據流分析為主的方向,而Semmle QL選擇了完善基礎和引擎。而當我們走到現在這個關口遇到瓶頸的時期,不妨嘗試看看別的思考思路,這也是這篇文章的初衷。

其核心的原理就在于通過把每一個操作具象化模板化,并儲存到數據庫中。比如

a($b);

這個語句被具象為

Function-a  FunctionCall ($b)

然后這樣的三元組我們可以作為數據庫中的一條數據。

而當我們想要在代碼中尋找執行a函數的語句時,我們就可以直接通過

select * from code_db from where type = 'FunctionCall' and node_name = 'Function-a';

這樣的一條語句可以尋找到代碼中所有的執行a函數的節點。

當然,靜態分析不可能僅靠這樣的簡單語句就找到漏洞,但事實就是,當我們針對CodeDB做分析的時候,我們既保證了強代碼執行順序,又可以跨越多重壁壘直接從sink點出發做分析,當相應的QL支持越來越多的高級查詢又或者是自定義高級規則之后,或許可以直接實現。

select * where {
    Source : $_GET,
    Sink : echo,
    is_filterxss : False,
}

也正是因為如此,CodeQL的出現,被許多人認為是跨時代的出現,靜態分析從底層的代碼分析,需要深入到編譯過程中的方式,變成了在平臺上巧妙構思的規則語句,或許從現在來說,CodeQL這種先鋪好底層的方式并不能直接的看到效果,可幸運的是,作為技術本身而言,我們又有了新的前進方向。

下面的文章,我們就跟著我前段時間的一些短期研究成果,探索一下到底如何實現一個合理的CodeDB。

如何實現一個合理的CodeDB呢?

在最早只有Semmle QL的時候我就翻看過一些paper,到后來的LGTM,再到后來的CodeQL我都有一些了解,后來CodeQL出來的時候,翻看過一些人寫的規則都距離CodeQL想要達到的目標相去甚遠,之后就一直想要自己試著寫一個類似的玩具試試看。這次在更新KunLun-M的過程中我又多次受制于基于AST的數據流分析的種種困難,于是有了這次的計劃誕生。

為了踐行我的想法,這次我花了幾個星期的事件設計了一個簡易版本的CodeDB,并基于CodeDB寫了一個簡單的尋找php反序列化鏈的工具,工具源碼詳見:

在聊具體的實現方案之前,我們需要想明白CodeDB到底需要記錄什么?

首先,每一行代碼的執行順序、所在文件是基本信息。其次當前代碼所在的域環境、代碼類型、代碼相關的信息也是必要的條件。

在這個基礎上,我嘗試使用域定位、執行順序、源節點、節點類型、節點信息這5個維度作為五元組儲存數據。舉一個簡單的例子:

test.php

<?php
$a = $_GET['a'];

if (1>0){
    echo $b;
}

上面的代碼轉化的結果為

test_php 1 Variable-$a Assignment ArrayOffset-$_GET@a
test_php 2 if If ['1', '>', '0']
test_php.if 0 1 BinaryOp-> 0
test_php.if 1 echo FunctionCall ('$a',)

由于這里我主要是一個嘗試,所以我直接依賴SQL來做查詢并將分析邏輯直接從代碼實現,這里我們直接用sql語句做查詢。

select * from code_db where node_type='FunctionCall' and node_name='echo'

用上述語句查詢出echo語句,然后分析節點信息得到參數為$a

然后通過

select * from code_db where node_locate = 'test_php.if' and node_sort=0 

來獲取if的條件信息,并判斷為真。

緊接著我們可以通過SQL語句為

select * from code_db where node_name='$a' and node_type='Assignment' and node_locate like 'test_php%' and node_id >= 4 

得到賦值語句,經過判斷就可以得到變量來源于$_GET

當然,邏輯處理遠比想像的要復雜,這里我們舉了一個簡單的例子做實例,通過sort為0記錄參數信息和條件信息,如果出現同一個語句中的多條指令,可能會出現sort相同的多個節點,還需要sort和id共同處理...

這里我嘗試性的構造了基于五元組的CodeDB生成方案,并通過一些SQL語句配合代碼邏輯分析,我們得到了想要掃描結果。事實上,雖然這種基于五元組的CodeDB仍不成熟,但我們的確通過這種方式構造了一種全新的掃描思路,如果CodeDB構造成熟,然后封裝一些基礎的查詢邏輯,我們就可以大幅度解決我在KunLun-M中遇到的許多困境。

寫在最后

這篇文章用了大量的篇幅解釋了什么是基于.QL的掃描方案,聊了聊許多現代代碼審計遇到的問題、困境。在這個基礎上,我也做了一些嘗試,這里講的這種基于五元組的CodeDB生成方案屬于我最近探索的比較有趣的生成方案,在這個基礎上,我也探索了一個簡單的查詢php反序列化的小插件,后續可能花費比較大的代價去做優化并定制一些基礎的查詢函數,希望這篇文章能給閱讀的你帶來一些收獲。

如果對相應的代碼感興趣,可以持續關注KunLun-M的更新

ref


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1387/