一、CodeQL的研發背景
最早期,安全人員會通過人工審計的方式來審計項目代碼,查找危險函數,並跟進危險函數的參數是否可控,如果可控,說明存在安全漏洞。
但是隨着項目數量的增加,以上的純靠人工的方式很難實現所有項目漏洞的覆蓋測試。所以出現了一些輔助人工審計的工具,比如前幾年比較火的rips,cobra,通過這些工具,可以把危險函數代碼代碼檢索出來,再通過人工審計來判斷是否存在安全漏洞。但是這種方式主要還是需要人來判斷,工作量還是很大,並且非常依賴安全工程師的個人能力。
但是近些年出現了不少優秀的自動化代碼安全審計產品,比如非常有名的Checkmarx,Fortify SCA。這些軟件可以自動化的幫我們審計出安全漏洞,大大減少了人工工作量,並加快了安全審計速度。但是這些軟件都是商業的,價格比較貴,一般企業可能沒有這麼多預算購買。
與此同時,Github爲了解決其託管的海量項目的安全性問題,收購了CodeQL的創業公司,並宣佈開源CodeQL的規則部分,這樣全世界的安全工程師就可以貢獻高效的QL審計規則給Github,幫助它解決託管項目的安全問題。對於安全工程師,也就多了一個非商業的開源代碼自動化審計工具。
CodeQL支持非常多的語言,在官網有如下支持的語言和框架列表
參考鏈接:
https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/
二、CodeQL簡介
CodeQL是一個可以對代碼進行分析的引擎,安全人員可以用它作爲挖洞的輔助或者直接進行挖掘漏洞,節省進行重複操作的精力。
在CodeQL中,代碼被解析成數據,存儲在數據庫中。安全漏洞、錯誤和其他錯誤被建模爲可以針對數據庫執行的查詢。我們可以運行由GitHub研究人員和社區貢獻者編寫的標準CodeQL查詢,也可以編寫自己的查詢以用於自定義分析。
0x1:CodeQL安裝、部署、簡單使用
CodeQL本身包含兩部分:
- 解析引擎:解析引擎用於解析數據庫執行查詢等操作。雖然不開源,但是我們可以直接在官網下載二進制文件直接使用,https://github.com/github/codeql-cli-binaries/releases
- SDK:SDK完全開源,裏面包含了規則查詢中涉及到的公共庫文件,針對不同語言提供了很多函數和類型以方便我們編寫自己的規則,https://github.com/github/codeql
CodeQL提供了命令行工具和vscode插件兩個選擇,vscode插件底層也是調用命令行工具,但是有圖形界面並且封裝了一些功能,用起來會更加方便。
注意,解析引擎和SDK要放在同級目錄,CodeQL引擎會自動在上下級目錄搜索庫。
1、codeql-cli安裝
這裏我們以命令行環境下運行codeql爲例,先下載codeql-cli,
項目地址 :
https://github.com/github/codeql-cli-binaries/releases
打開項目地址之後進入Releases庫,下載對應操作系統的壓縮包解壓到任意一個文件夾。
接下來安裝codeql規則庫,下載開源的codeql標準庫和查詢庫,
https://github.com/github/codeql/tree/main
保證codeql-cli(下圖中codeql文件夾)和codeql SDK(下圖中codeql-lib文件夾)在同一個目錄下,
2、vscode-codeql安裝
vscode的codeql插件,直接在插件市場安裝,
3、配置環境變量
爲了方便我們使用codeql-cli,我們需要將其路徑放到PATH下,
同時我們最好再配置下codeql插件的可執行文件路徑,打開vscode的設置,搜索codeql,修改Executable Path,
參考鏈接:
https://github.com/github/codeql-cli-binaries/releases https://github.com/github/codeql https://juejin.cn/post/6844903878694010893 https://ost.51cto.com/answer/5159
4、建立codeql workspace
兩種方法建立codeql workspace
- 第一種就是把要審計的代碼放入codeql中
- 第二種是把codeql加入要審計的代碼的workspace中(較麻煩)
這裏選擇第一種。
下載官方給出的codeql規則庫,starter,
git clone https://github.com/github/vscode-codeql-starter/
項目下載完成後,進入項目目錄,確保包含需要的子模塊。
git submodule update --init
git submodule update --remote
在VS Code中打開starter workspace,
starter子模塊中包括
- C/C++
- C#
- Java
- JavaScript
- Python
- Ruby
- GO的規則
5、創建codeql數據庫
現在codeql workspace設置好了,codeql規則庫庫也下來好了,接下來要準備被分析的項目project了,項目project是我們做代碼分析的主體。
由於CodeQL的處理對象並不是源碼本身,而是中間生成的AST結構數據庫,所以我們先需要把我們的項目源碼轉換成CodeQL能夠識別的CodeDatabase。如果你之前已經針對項目project創建好了codeql數據庫,在側邊欄打開CodeQL數據庫,如圖有四種添加數據庫的方法。
當添加數據庫之後,會有數據庫視圖,可以右擊列表中的項進行數據庫交互,可以利用Ctrl/Cmd+click選擇多個數據庫 。
這裏基於codeql案例庫中的java安全風險案例創建數據庫,/Users/zhenghan/Projects/codeql-lib/java/ql/test/query-tests/security/CWE-020
codeql database create java-security-CWE-020 -l=java -c="javac SuspiciousRegexpRange.java" --source-root=/Users/zhenghan/Projects/codeql-lib/java/ql/test/query-tests/security/CWE-020
也可以從零新建一個maven項目,然後基於這個項目創建codeql數據庫。
注意!生成數據庫之前,需要先保證被分析程序可以正常跑起來。
進入到項目根目錄下,執行codeql指令:
// 創建新數據庫 codeql database create java-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/codeql-home/hello_codeql // 更新數據庫 codeql database upgrade java-database
- codeql database create java-database:利用 codeql 創建名爲 java-database 的 java 數據庫
- -l=java:編譯語言爲 java
- -c="mvn clean install -file pom.xml":利用maven命令進行編譯
- --source-root=/Users/zhenghan/Projects/codeql-home/hello_codeql:設置生成codeql數據庫的路徑
將建好的codeql database導入vscode,
在該路徑增加一個 demo.ql,即可開始編寫ql查詢語句,
6、導入codeql數據庫,運行codeql查詢
和SQL語言一樣,我們執行QL查詢,肯定是要先指定一個數據庫纔可以。
待分析源碼如下,
package org.example; public class Main { public static void main(String[] args) { String a = "hello"; System.out.printf("Hello and welcome!"); if(1 == 1){ } for (int i = 1; i <= 5; i++) { System.out.println("i = " + i); } } }
將上面創建好的codeql數據庫導入vdcode。
因爲我們已經添加好了codeql workspace,所以在左邊側欄可以看到已經有官方內置現成的query .ql查詢文件可用了,點擊運行可以查看運行結果。
同時,我們也可以自行開發新的query查詢文件,用於自定義漏洞挖掘。
查詢程序中是否存在空代碼block,
參考鏈接:
https://docs.github.com/zh/code-security/codeql-cli/getting-started-with-the-codeql-cli/preparing-your-code-for-codeql-analysis https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/ https://www.anquanke.com/post/id/266823
0x2:CodeQL開發過程總結
在使用 CodeQL 分析代碼之前,需要創建一個 CodeQL 數據庫,其中包含對代碼運行查詢所需的所有數據。 可以使用 CodeQL CLI 自行創建 CodeQL 數據庫。
CodeQL 分析依賴於從代碼中提取關係數據,並使用它來生成 CodeQL 數據庫。 CodeQL 數據庫包含有關代碼庫的所有重要信息,可通過執行 CodeQL 查詢對其進行分析。
在生成 CodeQL 數據庫之前,需要:
- 安裝並設置 CodeQL CLI
- 查看要分析的代碼:
- 對於分支,請查看要分析的分支的頭。
- 對於拉取請求,請簽出拉取請求的頭部提交,或簽出 GitHub 生成的拉取請求的合併提交。
- 設置代碼庫的環境,確保所有依賴項都可用。
- 查找代碼庫的生成命令(如果有)。 通常可在 CI 系統的配置文件中找到。
代碼庫準備就緒後,可以運行 codeql database create 以創建數據庫。
0x3:CodeQL語法
CodeQL的很多語法和現在的主流高級語言有很多相似之處,但也有許多的不同。
舉一個簡單的例子,在CodeQL中不存在==,只有=,當一個變量定義了而沒有初始化的時候,=的意思是賦值,但當其已經被賦值了之後,=的意思就變成了比較。
1、基礎數據類型(Primitive types)
CodeQL 是一種靜態類型的語言,因此每個變量都必須有一個聲明的類型。類型是一組值。例如,int 類型是一組整數。注意,一個值可以屬於這些集合中的多個,這意味着它可以有多個類型。
- 整型(int)
- 浮點型(float)
- 日期型(date)
- 字符型(stirng)
- 布爾型(boolean)
簡單介紹下日期型和布爾型。
1)日期型(date)
編寫一個簡單的實例用於計算從今年9月1日到今天(11月2日)一共過了多久:
from date start, date end where start = "01/09/2021".toDate() and end = "02/11/2021".toDate() select start.daysTo(end)
2)布爾型(boolean)
布爾型變量用來存放布爾值,即false(假)或者 true(真)。
編寫一個簡單的例子來實現兩個布爾之間的和關係:
from boolean a, boolean b where a = true and b = false select a.booleanAnd(b)
2、謂詞(Predicates)
謂詞有點類似於其他語言中的函數,但又與函數不同,謂詞用於描述構成 QL 程序的邏輯關係。確切的說,謂詞描述的是給定參數與元組集合的關係。
1)無結果謂詞
沒有結果的謂詞以predicate作爲開頭,剩下的語法結構類似於定義函數。這種謂詞只能在where語句中使用。
一個簡單的例子如下:
predicate isCity(string city) { city = "Beijing" or city = "ShangHai" } from string city where city = "Beijing" and isCity(city) select city
2)結果謂詞
有結果的謂詞的定義類似於c/c++語言的函數定義,以返回類型替代predicate作爲開頭。這種謂詞可以在where與select語句中使用。
一個簡單的例子如下:
int addOne(int i) { result = i + 1 and i in [1 .. 10] } from int v where v = 9 select addOne(v)
3、綁定行爲與綁定集
謂詞所描述的集合通常不允許是無限的,換句話說,謂詞只能包含有限數量的元組(It must be possible to evaluate a predicate in a finite amount of time, so the set it describes is not usually allowed to be infinite. In other words, a predicate can only contain a finite number of tuples.)
舉個簡單的正例和反例:
// 正例,i被限定在1到10內,或者你也可以給i賦一個確定的值如i=1 int addOne(int i) { result = i + 1 and i in [1 .. 10] } // 反例,i是無限數量值的,此時CodeQL編譯器會報錯: 'i' is not bound to a value int addOne(int i) { result = i + 1 and i > 0 }
1)單個綁定集
爲了使上述的反例謂詞能夠通過編譯,我們可以使用綁定集(bindingset),但是當我們去調用這個謂詞時,傳遞的參數還是隻能在有限的參數集中。
上面的反例可以修改爲如下:
bindingset[i] int addOne(int i) { result = i + 1 and i > 0 } // 此時我們可以去調用這個謂詞,但是需要注意傳遞過來的參數還是隻能在有限的參數集中 from int i where i = 1 select addOne(i)
2)多個綁定集
我們同樣可以添加多個綁定集,下面是一個例子:
bindingset[x] bindingset[y] predicate plusOne(int x, int y) { x + 1 = y }
這個綁定集的意思是如果x或y綁定(bound)了,那麼x和y都綁定,即至少有一個參數受到約束。
如果我們想要兩者都受約束,可以將例子修改一下:
bindingset[x, y] predicate plusOne(int x, int y) { x + 1 = y }
那麼這個謂詞就變爲了一個類似於校驗的函數,即x+1 == y。
4、查詢(Query)
查詢是CodeQL的輸出。查詢有兩種類型,分別是
- select子句
- 查詢謂詞,這意味着我們可以在當前模塊中定義或者從其他模塊中導入
1)select子句
select子句的格式如下:
[from] /* ... variable declarations ... */ [where] /* ... logical formula ... */ select /* ... expressions ... */
其中from和where語句是可選的。我們可以在from中定義變量,在where中給變量賦值和對查詢結果的過濾,最後在select中顯示結果。
在select語句中我們還可以使用一些關鍵字:
- as關鍵字,後面跟隨一個名字。作用相當於sql中的as,爲結果列提供了一個"標籤",並允許在後續的select表達式中使用它們。
- order by關鍵字,後面跟隨一個一個結果列名。作用相當於sql中的order by,用於排序結果,並且在結果列名後可選asc(升序)或desc(降序)關鍵字。
一個簡單的例子如下:
from int x, int y where x = 3 and y in [0 .. 2] select x, y, x * y as product, "product: " + product
2)查詢謂詞
查詢謂詞是一個非成員謂詞,並在最開頭使用query作爲註解。它返回謂詞計算結果的所有元組,下面是一個簡單的示例:
query int getProduct(int x, int y) { x = 3 and y in [0 .. 2] and result = x * y }
編寫查詢謂詞而不是select子句的好處是我們可以在代碼的其他部分中調用謂詞。例如,我們可以在類中的特徵謂詞內部調用:
query int getProduct(int x, int y) { x = 3 and y in [0 .. 2] and result = x * y } class MultipleOfThree extends int { MultipleOfThree() { this = getProduct(_, _) } } from MultipleOfThree m select m
5、類(Classes)
我們可以在CodeQL中定義自己的類型,一個方法是定義一個類。
類提供了一種簡單的方法來重用和構造代碼。例如,我們可以:
- 在類中定義成員謂詞
- 定義子類以重寫成員謂詞
一個簡單的例子如下:
class OneTwoThree extends int { OneTwoThree() { // characteristic predicate this = 1 or this = 2 or this = 3 } string getAString() { // member predicate result = "One, two or three: " + this.toString() } predicate isEven() { // member predicate this = 2 } }
6、Method內置方法
import java from Method method where method.hasName("main") select method.getName(), method.getDeclaringType()
- method.getName() 獲取的是當前方法的名稱
- method.getDeclaringType() 獲取的是當前方法所屬class的名稱
- method.hasName() 判斷是否有該方法
7、設置Source-Sink污點傳播流
在代碼自動化安全審計的理論當中,有一個最核心的三元組概念,就是(source,sink和sanitizer)。
- source是指漏洞污染鏈條的輸入點。比如獲取http請求的參數部分,就是非常明顯的Source。
- sink是指漏洞污染鏈條的執行點,比如SQL注入漏洞,最終執行SQL語句的函數就是sink(這個函數可能叫query或者exeSql,或者其它)。
- sanitizer又叫淨化函數,是指在整個的漏洞鏈條當中,如果存在一個方法阻斷了整個傳遞鏈,那麼這個方法就叫sanitizer。
污點追蹤是CodeQL提供的一個非常強大的功能,也是進行代碼審計的基礎,CodeQL會分析代碼得到一張有向圖,參數和表達式就是裏面的節點,以下面一段代碼爲例子。
int func(int tainted) { int x = tainted; if (someCondition) { int y = x; callFoo(y); } else { return x; } return -1; }
有了這樣的圖我們可以藉此分析代碼參數的流向來尋找漏洞,庫提供了TaintTracking::Configuration這個類,我們需要繼承這個類,通過覆蓋實現isSource方法和isSink方法來設置起始點和終點,方法會提供dataflow::node參數,我們通過把邏輯加在節點上來設置我們想要的起點和終點,這樣CodeQL分析變量的流向,如果發現了有變量從source到sink,就可能會發現潛在的漏洞,比如從getParameter到query,這可能就是一個sql注入。
CodeQL還提供了更強大的功能,isSanitizer()方法可以讓我們設置淨化方法,設置一個節點,當流到達這個節點後中斷,比如replace()這樣的過濾函數,CodeQL並不知道他的作用,我們可以中斷調用了這個方法的數據流來降低誤報。
同樣的,CodeQL並不能識別全部的變量傳遞,這時候我們可以通過isAdditionalTaintStep()方法告訴污點追蹤把這兩個節點連起來。
1)設置source
override predicate isSource(DataFlow::Node src) {} // 通用的source入口規則 override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
2)設置Sink
override predicate isSink(DataFlow::Node sink) { } // 查找一個query()方法的調用點,並把它的第一個參數設置爲sink override predicate isSink(DataFlow::Node sink) { exists(Method method, MethodAccess call | method.hasName("query") and call.getMethod() = method and sink.asExpr() = call.getArgument(0) ) }
3)FLow數據流
連通工作就是CodeQL引擎本身來完成的。我們通過使用config.hasFlowPath(source, sink)方法來判斷是否連通。
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink where config.hasFlowPath(source, sink) select source.getNode(), source, sink, "source" //我們傳遞給config.hasFlowPath(source, sink)我們定義好的source和sink,系統就會自動幫我們判斷是否存在漏洞了
4)使用jdbcTemplate.query方法的SQL注入
import java import semmle.code.java.dataflow.FlowSources import semmle.code.java.security.QueryInjection import DataFlow::PathGraph class VulConfig extends TaintTracking::Configuration { VulConfig() { this = "SqlinjectionConfig" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { exists(Method method, MethodAccess call | method.hasName("query") and call.getMethod() = method and sink.asExpr() = call.getArgument(0)) } } from VulConfig vulconfig, DataFlow::PathNode source, DataFlow::PathNode sink where vulconfig.hasFlowPath(source, sink) select source.getNode(), source, sink, "source"
參考鏈接:
https://longlone.top/%E5%AE%89%E5%85%A8/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/codeql/2.CodeQL%E8%AF%AD%E6%B3%95/
0x4:codeql審計代碼原理圖
原理:編寫查詢語句找出代碼中的漏洞,codeql 內的編譯器調用 extractor 將 java 代碼編譯成可查詢的數據流,並以數據庫的形式搭配 ql 庫與編寫的查詢語句進行查詢,得出結果並生成報告。
CodeQL的查詢需要建立在一個數據庫的基礎之上,這個數據庫是通過Extractor模塊對源代碼進行分析、提取後得到的。數據庫建立之後,我們就可以使用CodeQL去探索源碼,並發現代碼中的一些已知問題。
- 對於編譯型語言,CodeQL會在建立數據庫時“模擬”編譯的過程,在make等編譯工具鏈調用gcc等編譯器時,用相同的編譯參數調用extractor模塊取而代之,收集源代碼的所有相關信息,如AST抽象語法樹、函數變量類型、預處理器操作等等。
- 對於解釋型語言,因爲沒有編譯器的存在,CodeQL會以跟蹤執行的方式獲取類似的信息。
使用CodeQL CLI對代碼倉庫運行分析後,我們就得到了一個“快照數據庫”(SnapshotDatabase),這個數據庫中存儲了代碼倉庫在特定時間點(數據庫建立時)的層級表示方式,包括
- AST語法樹
- CFG控制流程關係
- DFG數據流向關係
在這個數據庫中,代碼中的每一個要素,比如函數定義(Function)、函數調用(FunctionCall)、宏調用(MacroInvocation)都是可以被檢索的實體。在這些基礎上,我們再編寫CodeQL語句對代碼進行分析。
查詢包括上圖查詢編譯部分和執行部分,我們的查詢會和庫一起交給編譯器編譯,編譯成功後會進行查詢,去數據庫中提取數據。
參考鏈接:
https://github.com/ASTTeam/CodeQL#02-codeql%E5%9F%BA%E7%A1%80 https://www.sec-in.com/article/2043 https://cloud.tencent.com/developer/article/1645870 https://www.wangan.com/p/7fy7fg448fb3b026
三、CodeQL進階
0x1:一些開源的優秀CodeQL規則
codeql的核心在於它的規則。
- 《My CodeQL queries collection》
- https://github.com/cor0ps/codeql
- https://github.com/GeekMasher/security-queries
- https://github.com/Marcono1234/codeql-java-queries
- https://github.com/imagemlt/myQLrules
- https://github.com/advanced-security/codeql-queries
- https://github.com/jenkins-infra/jenkins-codeql
- https://github.com/ice-doom/CodeQLRule
- https://github.com/zbazztian/codeql-queries
0x2:提升挖掘效率的一些高級QL技巧
1、一些語法技巧
1)獲取具體QL類型
不確定使用什麼方式獲取目標時,除了通過查看AST,還可以通過詞getAQlClass()獲取調用它實體的具體QL類型。
from Expr e, Callable c where e.getEnclosingCallable() = c select e, e.getAQlClass()
2)儘可能縮小範圍
如下定義,如果項目代碼量很大,則非常耗時,
override predicate isSink(DataFlow::Node sink) { sink.asExpr().getParent() instanceof ReturnStmt }
可以設置return語句在哪個函數中調用來縮小範圍,乃至其Type的全限定名,
override predicate isSink(DataFlow::Node sink) { sink.asExpr().getParent() instanceof ReturnStmt and sink.asExpr().getEnclosingCallable().hasName("xxxxx") }
以某個方法的參數作爲source (添加了幾種過濾方式,第一個參數、該方法當前類的全限定名爲xxxx),
override predicate isSource(DataFlow::Node source) { exists(Parameter p | p.getCallable().hasName("readValue") and source.asParameter() = p and source.asParameter().getPosition() = 0 and p.getCallable().getDeclaringType().hasQualifiedName("com.service.impl", "xxxxx") ) }
以某個實例的所有參數作爲source(`X1 x1 = new X1(a,b)`,這裏a、b作爲source),過濾:調用該實例的方法名稱爲`Caller`,實例類型名稱爲`X1`,
override predicate isSource(DataFlow::Node source) { exists(ClassInstanceExpr ma | source.asExpr() = ma.getAnArgument() and ma.getTypeName().toString() = "X1" and ma.getCaller().hasName("Caller") ) }
3)調用端點路徑
比如我們想知道方法A到方法G之間調用端點路徑,則可以使用edges謂詞,編寫如下所示,如果也想找覆寫的某個方法(如:接口實現類中的方法)可以將calls替換爲polyCalls,
import java class StartMethod extends Method { StartMethod() { getName() = "main" } } class TargetMethod extends Method { TargetMethod() { getName() = "vulMain" } } query predicate edges(Method a, Method b) { a.calls(b) } from TargetMethod end, StartMethod entryPoint where edges+(entryPoint, end) select end, entryPoint, end, "Found a path from start to target."
待分析的源碼如下,
package org.example; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { Main error = new Main(); error.readValue("open -a Calculator"); } private void readValue(String comm) throws IOException { vulMain(comm); taintVulMain(comm); } private void taintVulMain(String comm) { Runtime rt = Runtime.getRuntime(); rt.getClass(); } private void vulMain(String comm) throws IOException { Runtime rt = Runtime.getRuntime(); rt.exec(comm); } }
生成codeql數據庫,
cd /Users/zhenghan/Projects/codeql-home/hello_codeql codeql database create hello_codeql-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/codeql-home/hello_codeql
4)對某接口實現
主要是通過codeql自帶謂詞overridesOrInstantiates判斷該函數是否進行了重寫。
如下,就能獲取實現JSONStreamAware接口,重寫的方法
class JsonInterface extends Interface{ JsonInterface(){ this.hasQualifiedName("com.alibaba.fastjson", "JSONStreamAware") } Method getJsonMethod(){ result.getDeclaringType() = this } } class CMethod extends Method{ CMethod(){ this.overridesOrInstantiates*(any(JsonInterface i).getJsonMethod()) } } from CMethod m select m, m.getDeclaringType()
2、AdditionalTaintStep
在爲一些項目編寫規則查詢時,經常碰到數據流中斷的情況,下面列出經常碰到中斷的情況和解決方案。
1)setter和getter
CodeQL爲減少誤報很多地方都需要我們根據相應場景自己連接數據流,比如getter。
這種情況需要將調用方法的對象(通過getQualifier謂詞獲取限定符)和調用方法的返回值連接起來。如下操作就是從get%方法訪問到它的限定符作爲附加步驟重新連接起來。
class GetSetTaintStep extends TaintTracking::AdditionalTaintStep{ override predicate step(DataFlow::Node src, DataFlow::Node sink){ exists(MethodAccess ma | (ma.getMethod() instanceof GetterMethod or ma.getMethod() instanceof SetterMethod or ma.getMethod().getName().matches("get%") or ma.getMethod().getName().matches("set%")) and src.asExpr() = ma.getQualifier() and sink.asExpr() = ma ) } }
2)mapper
使用mybatis通常將接口命名爲xxxxMapper或者xxxxDao這種形式,在xml配置文件中通過namespace指定其全限定名,當數據流需要經過數據庫查詢到這裏會斷開,那麼需要手動將其連接起來。
3)污染源作爲參數傳入
如下圖所示,instance作爲污染源,workNode也被污染,將其傳入t.setSceneKey爲t對象的sceneKey屬性賦值,那麼這裏t對象理應也是被污染的。但當我們將instance作爲source,return t作爲sink是獲取不到路徑的,
圖片來自https://xz.aliyun.com/t/10852#toc-8
要解決這個問題,需要加上額外3個步驟。
- 將調用方法的所有參數作爲source(圖中setSceneKey方法的workNode.getSceneKey()參數),將調用方法的對象作爲sink(圖中的t對象),代碼如下
class SrcTaintStep extends TaintTracking::AdditionalTaintStep{ override predicate step(DataFlow::Node src, DataFlow::Node sink){ exists(MethodAccess ma | (ma.getMethod() instanceof SetterMethod or ma.getMethod().getName().matches("set%")) and src.asExpr() = ma.getAnArgument() and sink.asExpr() = ma.getQualifier() ) } }
-
instance的getter
-
workNodeMapper
4)實例化
如下圖,將req傳入UploadFile中創建UploadFile對象,再將其傳入systemService.uploadFile方法中,這種情況,uploadFile對象應該是受污染的,但是默認情況下,我們想讓數據流進入systemService.uploadFile中是不行的,因爲在new UploadFile就已經斷開了。那麼就需要將其連接起來。
代碼如下,如果已經知道當前查詢大概斷的位置,可以縮小範圍,這裏將所有的都會連接起來,
class InstanceTaintStep extends TaintTracking::AdditionalTaintStep{ override predicate step(DataFlow::Node src, DataFlow::Node sink){ exists(ClassInstanceExpr cie | // cie.getTypeName().toString() = "UploadFile" src.asExpr() = cie.getAnArgument() and sink.asExpr() = cie) } }
參考鏈接:
https://github.com/ASTTeam/CodeQL#02-codeql%E5%9F%BA%E7%A1%80 https://xz.aliyun.com/t/10852#toc-8
四、CodeQL案例學習
這個章節,我們通過一些具體的項目,利用CodeQL挖掘復現一些已知的Nday漏洞,目的是提高對CodeQL的理解。
0x1:micro_service_seclab靶場漏洞復現
這是一個Java漏洞靶場,基於SpringBoot開發,目的是用來檢測SAST工具的準確性(關注漏報和誤報問題)的。可以用此靶場測試(CodeQL, CheckMarx, Fortify SCA)白盒檢測工具,根據預先埋點的漏洞,與測試結果進行對比,判斷在什麼地方存在誤報和漏報的問題。
1、靶場支持的漏洞
1)SQL注入
SQL注入這部分,會出現很多不同白盒寫法導致的SQL注入。
種類 | 解釋 | 僞代碼 |
---|---|---|
String Source | 輸入點是字符串類型 | one(@RequestParam(value = "username") String username) |
List<Long> | 輸入點是Long泛型(用來測試誤報) | longin(@RequestBody List<Long> user_list) |
Optional<String> | 新特性 | optionalLike(@RequestParam(value = "username") Optional<String> optinal_username) |
List<String> Source | 輸入點是String泛型 | in(@RequestBody List<String> user_list) |
Object Source | 對象類型 | objectParam(@RequestBody Student user) |
MyBatis注入 | XML分離SQL檢測 | myBatis(@RequestParam(value = "name") String name) |
In類型注入 | In類型注入 | 參照代碼 |
Like類型 | Like類型注入 | 參照代碼 |
Lombok | Lombok對注入漏洞的影響 | 參照代碼 |
MyBatis註解方式注入 | MyBatis註解方式注入 | 參照代碼 |
Spring Data JPA | JPA 方式 | 參照代碼 |
2)RCE命令執行
種類 | 解釋 | 僞代碼 |
---|---|---|
processBuilder | processBuilder導致的RCE | -- |
Runtime.getRuntime().exec(args) | Runtime.getRuntime().exec(args)導致的RCE | -- |
3)FastJson反序列化漏洞
提供1.2.31版本的Fastjson供進行測試。
@RestController @RequestMapping(value = "/fastjson") public class FastJsonController { @PostMapping(value = "/create") public Teacher createActivity(@RequestBody String applyData, HttpServletRequest request, HttpServletResponse response){ Teacher teachVO = JSON.parseObject(applyData, Teacher.class); return teachVO; } }
4)SSRF漏洞
種類 | 解釋 | 僞代碼 |
---|---|---|
url.openConnection() | url.openConnection()引起的SSRF | 參照代碼 |
Request.Get() | Request.Get()引起的SSRF | 參照代碼 |
OkHttpClient | OkHttpClient引起的SSRF | 參照代碼 |
DefaultHttpClient | DefaultHttpClient引起的SSRF | 參照代碼 |
url.openStream() | url.openStream()引起的SSRF | 參照代碼 |
5)XXE漏洞
種類 | 解釋 | 僞代碼 |
---|---|---|
DocumentBuilderFactory | DocumentBuilderFactory引起的SSRF | 參照代碼 |
2、基本分析過程
CodeQL的核心引擎是不開源的,這個核心引擎的作用之一是幫助我們把micro-service-seclab轉換成CodeQL能識別的中間層數據庫。
然後我們需要編寫QL查詢語句來獲取我們想要的數據。
由於CodeQL開源了所有的規則和規則庫部分,所以我們能夠做的就是編寫符合我們業務邏輯的QL規則,然後使用CodeQL引擎去跑我們的規則,發現靶場的安全漏洞。
3、生成codeql數據庫
- OS: Mac
- Java JDK: 1.8.0_291, vendor: Oracle Corporation
- Maven: Apache Maven 3.9.4 (dfbb324ad4a7c8fb0bf182e6d91b0ae20e3d2dd9)
cd /Users/zhenghan/Projects/codeql-home/micro_service_seclab codeql database create micro-service-seclab-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/codeql-home/micro_service_seclab
參考鏈接:
https://github.com/l4yn3/micro_service_seclab/ https://blog.gm7.org/%E4%B8%AA%E4%BA%BA%E7%9F%A5%E8%AF%86%E5%BA%93/02.%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/03.codeql/01.codeql%E5%85%A5%E9%97%A8.html https://www.freebuf.com/articles/web/283795.html
0x2:基於CodeQL分析XXL-job
xxl-job漏洞原理及編譯部署可以參閱這篇文章。
生成codeql數據庫,
cd /Users/zhenghan/Projects/xxl-job_2.4.0 codeql database create xxl-job-database -l=java -c="mvn clean install -file pom.xml" --source-root=/Users/zhenghan/Projects/xxl-job_2.4.0
導入vscode,
開始構建code ql語句,
import java from MethodAccess ma, Method m where m = ma.getMethod() and m.getName().regexpMatch("equals|getResourceAsStream|getResourceAsStream|getSystemResourceAsStream|ClassPathResource|BufferedInputStream|FileInputStream|getSystemResourceAsStream|getBundle") and not m.getDeclaringType().getName().matches("SecureUtil|WhiteListedClass") select ma, "Risky method " + m.getName()
上述語句使用簡單的AST匹配模式檢測危險函數的方法,匹配等式判斷、配置讀取等函數。
通過查詢結果找到讀取配置的位置,即讀取配置函數的定位。
繼續往上追溯loadProperties的調用源頭,
讀取配置的屬性包括addresses、accessToken、appname、addres、ip、port、logpath、logretentiondays等,如前文所述,accessToken身份繞過漏洞就是accessToken配置值和API請求的XXL-JOB-ACCESS-TOKEN一致通過的校驗。
繼續尋找與上述參數相關的代碼,
找到上述方法的構造出POST或者GET請求方法,即可構造出漏洞。
參考鏈接:
https://mp.weixin.qq.com/s?__biz=Mzg4Nzk3MTg3MA==&mid=2247484600&idx=1&sn=820df60a885378f30f4ffd9a407308a8&scene=21#wechat_redirect
0x3:基於CodeQL分析Shiro
1、maven編譯shiro相關問題
關於shiro源碼編譯及部署可以參閱這篇文章。
下載安裝jdk1.7,https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html
/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/bin/java -version
修改maven toolchains配置文件,修改maven使用jdk1.7進行編譯,
cat /usr/local/Cellar/maven/3.9.4/libexec/conf/toolchains.xml
修改內容爲,本地的jdk的java_home,以及對應jdk版本,注意這裏可以寫多個jdk版本,只要本地有:
<toolchain> <type>jdk</type> <provides> <version>1.8</version> <vendor>sun</vendor> </provides> <configuration> <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/</jdkHome> </configuration> </toolchain> <toolchain> <type>jdk</type> <provides> <version>1.7</version> <vendor>sun</vendor> </provides> <configuration> <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/</jdkHome> </configuration> </toolchain>
拷貝toolchains.xml文件,
cp /usr/local/Cellar/maven/3.9.4/libexec/conf/toolchains.xml ~/.m2/toolchains.xml
接下來修改根目錄下pom.xml文件中toolchains配置,修改對應版本爲剛纔mvn的配置文件中指定一個版本,這裏必須是剛纔配置的jdk版本中有的版本。
驗證一下maven編譯通過。
運行codeql指令生成數據庫,
cd /Users/zhenghan/Projects/shiro-shiro-root-1.2.4 codeql database create shiro-samples-database -l=java -c="mvn -e clean install -Dmaven.test.skip=true -pl samples -am" --source-root=/Users/zhenghan/Projects/shiro-shiro-root-1.2.4
參考鏈接:
https://www.anquanke.com/post/id/256967 https://blog.csdn.net/gzt19881123/article/details/106487550 https://blog.csdn.net/qq_38376348/article/details/108962790 https://blog.csdn.net/yiqiu3812/article/details/103298980 https://stackoverflow.com/questions/40354942/maven-build-error-after-setting-toolchain-right https://blog.csdn.net/qq_20042935/article/details/106540753 https://www.anquanke.com/post/id/255721#h2-9
0x4:基於CodeQL分析Log4j
參考鏈接:
https://www.anquanke.com/post/id/255721 https://www.freebuf.com/articles/web/318141.html https://mp.weixin.qq.com/s/JYco8DysQNszMohH6zJEGw
0x5:基於CodeQL分析SecExample
參考鏈接:
https://github.com/tangxiaofeng7/SecExample
五、CodeQL和大模型技術的結合機會
參考鏈接:
https://mp.weixin.qq.com/s/xlUWn2oWU51NVkgB157pRw https://mp.weixin.qq.com/s/Ix2lArBzaCAJr5nyGolCwQ https://mp.weixin.qq.com/s/leLFECUaNOGbjsN_8mcXrQ https://mp.weixin.qq.com/s/Masyfq12cjaM4Zn6qxvGoA https://mp.weixin.qq.com/s/QIKvRzNlAKiqh_UMOMfDdg https://www.trendmicro.com/ja_jp/devops/23/e/chatgpt-security-vulnerabilities.html https://blog.csdn.net/ljqclqjc/article/details/133899983