程式碼分析引擎 CodeQL 初體驗

酷酷的曉得哥發表於2019-11-19

作者:w7ay@知道創宇404實驗室

日期:2019年11月18日

原文連結:


QL是一種查詢語言,支援對C++,C#,Java,JavaScript,Python,go等多種語言進行分析,可用於分析程式碼,查詢程式碼中控制流等資訊。

之前筆者有簡單的研究透過JavaScript語義分析來查詢XSS,所以對於這款引擎有濃厚的研究興趣 。

安裝

1.下載分析程式:

分析程式支援主流的作業系統,Windows,Mac,Linux

2.下載相關庫檔案:

庫檔案是開源的,我們要做的是根據這些庫檔案來編寫QL指令碼。

3.下載最新版的VScode,安裝CodeQL擴充套件程式:

  • 用vscode的擴充套件可以方便我們看程式碼

  • 然後到擴充套件中心配置相關引數

4.

  • cli填寫下載的分析程式路徑就行了,windows可以填寫codeql.cmd
  • 其他地方預設就行

建立資料庫

以JavaScript為例,建立分析資料庫,建立資料庫其實就是用分析程式來分析原始碼。到要分析原始碼的根目錄,執行 codeql database create jstest --language=javascript

接下來會在該目錄下生成一個 jstest的資料夾,就是資料庫的資料夾了。

接著用vscode開啟之前下載的ql庫檔案,在ql選擇夾中新增剛才的資料庫檔案,並設定為當前資料庫。

接著在QL/javascript/ql/src目錄下新建一個test.ql,用來編寫我們的ql指令碼。為什麼要在這個目錄下建立檔案呢,因為在其他地方測試的時候 import javascript匯入不進來,在這個目錄下,有個 javascript.qll就是基礎類庫,就可以直接引入 import javascript,當然可能也有其他的方法。

看它的庫檔案,它基本把JavaScript中用到的庫,或者其他語言的定義語法都支援了。

輸出一段hello world試試?

語義分析查詢的原理

剛開始接觸ql語法的時候可能會感到它的語法有些奇怪,它為什麼要這樣設計?我先說說自己之前研究基於JavaScript語義分析查詢dom-xss是怎樣做的。

首先一段類似這樣的javascript程式碼

var param = location.hash.split("#")[1];document.write("Hello " + param + "!");

常規的思路是,我們先找到 document.write函式,由這個函式的第一個引數回溯尋找,如果發現它最後是 location.hash.split("#")[1];,就尋找成功了。我們可以稱 document.writesink,稱 location.hash.splitsource。基於語義分析就是由sink找到source的過程(當然反過來找也是可以的)。

而基於這個目標,就需要我們設計一款理解程式碼上下文的工具,傳統的正則搜尋已經無法完成了。

第一步要將JavaScript的程式碼轉換為語法樹,透過 pyjsparser可以進行轉換

from pyjsparser import parseimport json
html = '''
    var param = location.hash.split("#")[1];document.write("Hello " + param + "!");
    '''
js_ast = parse(html)print(json.dumps(js_ast)) # 它輸出的是python的dict格式,我們用轉換為json方便檢視

最終就得到了如下一個樹結構

這些樹結構的一些定義可以參考:

大概意思可以這樣理解:變數 param是一個 Identifier型別,它的初始化定義的是一個 MemberExpression表示式,該表示式其實也是一個 CallExpression表示式, CallExpression表示式的引數是一個 Literal型別,而它具體的定義又是一個 MemberExpression表示式。

第二步,我們需要設計一個遞迴來找到每個表示式,每一個 Identifier,每個 Literal型別等等。我們要將之前的 document.write轉換為語法樹的形式

{
"type":"MemberExpression",
  "object":{
    "type":"Identifier",
    "name":"document"
  },
  "property":{
    "type":"Identifier",
    "name":"write"
  }
}

location.hash也是同理

{
  "type":"MemberExpression",
  "object":{
    "type":"Identifier",
    "name":"location"
  },
  "property":{
    "type":"Identifier",
    "name":"hash"
  }
}

在找到了這些 sinksource後,再進行正向或反向的回溯分析。回溯分析也會遇到不少問題,如何處理物件的傳遞,引數的傳遞等等很多問題。之前也基於這些設計寫了一個線上基於語義分析的

QL語法

QL語法雖然隱藏了語法樹的細節,但其實它提供了很多類似 , 函式的概念來幫助我們查詢相關'語法'。

依舊是這段程式碼為例子

var param = location.hash.split("#")[1];document.write("Hello " + param + "!");

上文我們已經建立好了查詢的資料庫,現在我們分別來看如何查詢sink,source,以及怎樣將它們關聯起來。

我也是看它的文件: 學習的,它提供了很多方便的函式,我沒有仔細看。我的查詢語句都是基於語法樹的查詢思想,可能官方已經給出了更好的查詢方式,所以看看就行了,反正也能用。

查詢 document.write

import javascript
from Expr dollarArg,CallExpr dollarCall
where dollarCall.getCalleeName() = "write" and
    dollarCall.getReceiver().toString() = "document" and
    dollarArg = dollarCall.getArgument(0)
select dollarArg

這段語句的意思是查詢document.write,並輸出它的第一個引數

查詢 location.hash.split

import javascript
from CallExpr dollarCall
where dollarCall.getCalleeName() = "split" and
    dollarCall.getReceiver().toString() = "location.hash"
select dollarCall

查詢location.hash.split並輸出

資料流分析

接著從 sink來找到 source,將上面語句組合下,按照官方的文件來就行

class XSSTracker extends TaintTracking::Configuration {
  XSSTracker() {
    // unique identifier for this configuration
    this = "XSSTracker"
  }
  override predicate isSource(DataFlow::Node nd) {
   exists(CallExpr dollarCall |
      nd.asExpr() instanceof CallExpr and
      dollarCall.getCalleeName() = "split" and
      dollarCall.getReceiver().toString() = "location.hash" and
      nd.asExpr() = dollarCall
    ) 
  }
  override predicate isSink(DataFlow::Node nd) {
    exists(CallExpr dollarCall |
      dollarCall.getCalleeName() = "write" and
      dollarCall.getReceiver().toString() = "document" and
      nd.asExpr() = dollarCall.getArgument(0)
    )
  }
}
from XSSTracker pt, DataFlow::Node source, DataFlow::Node sink
where pt.hasFlow(source, sink)
select source,sink

將source和sink輸出,就能找到它們具體的定義。

我們找到查詢到的樣本

可以發現它的回溯是會根據變數,函式的返回值一起走的。

當然從source到sink也不可能是一馬平川的,中間肯定也會有阻擋的條件,ql官方有給出解決方案。總之就是要求我們更加細化完善ql查詢程式碼。

接下來放出幾個查詢還不精確的樣本,大家可以自己嘗試如何進行查詢變得精確。

var custoom = location.hash.split("#")[1];
var param = '';
param = " custoom:" + custoom;
param = param.replace('<','');
param = param.replace('"','');
document.write("Hello " + param + "!");
quora = {
    zebra: function (apple) {
        document.write(this.params);
    },
    params:function(){
        return location.hash.split('#')[1];
    }
};
quora.zebra();

最後

CodeQL將語法樹抽離出來,提供了一種用程式碼查詢程式碼的方案,更增強了基於資料分析的靈活度。唯一的遺憾是它並沒有提供很多查詢漏洞的規則,它讓我們自己寫。這也不由得讓我想起另一款強大的基於語義的程式碼審計工具fortify,它的規則庫是公開的,將這兩者結合一下說不定會有不一樣的火花。

Github公告說將用它來搜尋開源專案中的問題,而作為安全研究員的我們來說,也可以用它來做類似的事情?


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912109/viewspace-2664799/,如需轉載,請註明出處,否則將追究法律責任。

相關文章