CodeQL的自動化程式碼審計之路(上篇)
0x01 前言
最近關於CodeQL的概念很火,大家普遍認為這會是下一代的程式碼審計神器。網上關於CodeQL的文章已經有不少,但是多數文章還是在分析CodeQL的安裝和簡單使用用例。真正使用CodeQL來進行自動化程式碼審計的文章較少,本文主要研究基於CodeQL實現全自動的程式碼審計工具實現思路,預計文章分成三部分完成,目前是第一部分內容。
CodeQL(全稱Code Query Language),從其英文名稱中可以看出這是一種基於程式碼的查詢語言,其作用主要是透過編寫好的語句查詢程式碼中可能存在的安全隱患。學習CodeQL類似於學習一門全新的程式語言,語法類似於SQL,但是比傳統SQL還是要難很多。目前CodeQL支援對多種語言,包括java、javascript、go、python、C、Csharp等,但是很遺憾的是不支援“世界上最好的語言”PHP。這大概是因為PHP實在是太靈活了,函式名是字串變數這種呼叫方式確實很難從AST語法樹中靜態分析出問題,但這並不能阻礙我們學習CodeQL的興趣。文章所有內容基本上圍繞java語言展開,其他語言操作基本類似。
0x02 環境準備
網上關於CodeQL安裝的文章已經很多了,本來不打算再說這個事情,但是因為本人在CodeQL安裝過程中遇到不相容mac m1架構的情況,我想還有很多小夥伴也會遇到這個問題的,這裡主要以MAC的環境來說明安裝過程。
CodeQL的安裝主要分成引擎和SDK,新建一個目錄CodeQL(~/CodeQL/)來儲存後續所有的相關的工具和程式碼。
首先下載最新的引擎包,下載地址是:https://github.com/github/codeql-cli-binaries/releases
下載之後解壓把codeql資料夾放在剛才新建的資料夾CodeQL中,新增環境變數。
vim ~/.profile
export PATH=/Users/使用者名稱/CodeQL/codeql:${PATH}
使用source命令是環境變數生效,然後命令列中執行codeql,如圖2.1所示。
圖2.1 CodeQL引擎安裝
然後需要下載CodeQL對應的sdk包,下載地址是:
https://github.com/Semmle/ql
下載之後也需要把ql資料夾複製到~/CodeQL資料夾中。
在CodeQL資料夾中新建databases資料夾,用於存放後續使用codeql生成的資料庫,那麼一切準備好了之後我們的CodeQL目錄之下就會是三個資料夾,如圖2.2所示。
圖2.2 CodeQL安裝
後續我們就可以使用codeql database create命令來建立查詢資料庫,命令如下所示。
codeql database create /Users/xxx/CodeQL/databases/project_db_name --language=java --source-root=/Users/xxx/cms/project_path --overwrite
在windows環境中和以前的mac環境中確實沒有問題,但是如果是在m1的環境中會報錯,報錯資訊如圖2.3所示。錯誤的原因是codeql官方提供的工具是x86架構的,不能直接在arm中使用。
圖2.3 在MAC M1環境中codeql執行錯誤
從官網中找到了codeql對m1的支援情況,如圖2.4所示。從圖中可以明確看出codeql確實是支援m1架構的,但是需要依賴rosetta2和xcode。但是並沒有給出具體的安裝和使用步驟,必須吐槽官方一點也不人性化,說話說一半。
圖2.4 CodeQL支援M1架構
後來慢慢摸索著裝xcode和rosetta2,安裝xcode是直接透過appstore來裝的,安裝rosetta2是使用下面的命令。
softwareupdate –install-rosetta
安裝好了之後就可以使用下面的命令來生成資料庫,與傳統方式不同的是需要在命令前面增加arch -x86_64,如圖2.5所示。
arch -x86_64 codeql database create /Users/xxx/CodeQL/databases/mvn_test --language=java --command='mvn clean install -DskipTests' --source-root=/Users/xxx/java/projects/mvn_test --overwrite
圖2.5 在M1中使用codeql生成資料庫
0x03 語法基礎
CodeQL是一門全新的語言,基礎的CodeQL語法網上已經有很多文章。大家在學習之前可以首先參考連結,瞭解關於CodeQL的基礎語法,重點掌握關於類和謂詞的概念。
參考連結: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/
直接來學習語法是一件很枯燥的事情,我們這裡只是總結一些CodeQL中重點的概念。關於語法詳情在後續的實際案例分析中會有更深刻的體會。
1) 與Class相關的概念
與類直接相關的概念包括Class、Method、Field、Constructor,其代表的意義與java語言一致,透過其相互組合可以從資料庫中篩選出符合條件的類和方法。
Demo1: 查詢類的全限定名中包含Person的類,其中方法getQualifiedName代表獲取類對應的全限定類名。
import java
from Class c
where c.getQualifiedName().indexOf("Person") >=0
select c.getQualifiedName()
Demo2: 查詢所有欄位Field,滿足條件是欄位型別是public,並且欄位型別繼承java.lang.Throwable。(Fastjson1.2.80漏洞利用鏈的查詢方式)。
其中getASupertype代表獲取類對應的父類,*代表遞迴查詢所有父類。
getDeclaringType代表獲取欄位對應的定義型別。
getAModifier代表獲取欄位對應的修飾符。
import java
from Class c, Field f
where c.getASupertype*().hasQualifiedName("java.lang", "Throwable") and
f.getDeclaringType() = c and
f.getAModifier().getName() = "public"
select c.getQualifiedName(),f.getName()
2) 與Access相關的概念
access代表對變數或者方法的呼叫,主要有VarAccess和MethodAccess。
Demo1:查詢所有繼承自java.util.list的變數及變數的引用。
import java
from RefType t,Variable v,VarAccess va
where t.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", "List") and
v.getType() = t and
va.getVariable() = v
select v,va
Demo2:查詢所有InputStream類對應的readObject方法呼叫(遍歷反序列化漏洞的基礎)。
import java
from MethodAccess ma,Class c
where ma.getMethod().hasName("readObject") and
ma.getQualifier().getType() = c and
c.getASupertype*().hasQualifiedName("java.io", "InputStream")
select ma,ma.getEnclosingCallable()
3)與Type相關的概念
Type代表型別,是屬於CodeQL中一個很重要的概念,Type類有倆個直接派生類PrimitiveType,RefType。
PrimitiveType代表Java中的基礎資料型別,派生類有boolean, byte, char, double, float, int, long, short, void,, null。
RefType代表Java中的引用型別,有派生類Class、Interface、EnumType、Array。
Type多數情況下是和Acess相互使用的,其實在上面Acess的例子中幾乎都用到了Type相關的類。
4)與Flow相關的概念
Flow是CodeQL中最重要的概念,代表資料流,與此對應的概念包括source和sink。
source代表可控的使用者輸入點,通常是指WEB站點中的URL中引數,例如
request.getParameter("name")。其他例如命令列引數args也屬於source。在CodeQL中已經存在RemoteFlowSource類,在類中已經定義了很多常見的source點,可以滿足我們做一般性程式碼審計的需要。但是如果我們是要做特定jar包漏洞挖掘,例如復現log4j2的遠端命令執行漏洞,由於log4j2包中不包含常規的source點,就需要使用者自定義source。
sink代表危險的函式,通常是指一些危險的操作,包括命令執行、程式碼執行、jndi注入、SQL隱碼攻擊、XML注入等。CodeQL雖然也預置了部分的sink點,但是遠不能滿足實際的需求,需要我們在不同的漏洞環境中自定義sink點。
在有了source和sink之後我們可以基於CodeQL提供的查詢機制,自動判斷是否存在flow可以連線source和sink,一個典型的用法如下,如圖3.1所示。
圖3.1 典型的flow利用方式
在圖3.1所示的Flow中,自定義類繼承自TaintTracking::Configuration,並且覆蓋其中的isSource個isSink方法。這個是固定寫法,後續的絕大部分的ql指令碼都包含這樣的程式碼。
其中isAdditionalTaintStep方法是CodeQL的類TaintTracking::Configuration提供的的方法,它的原型是:override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {}。它的作用是將一個可控節點A強制傳遞給另外一個節點B,那麼節點B也就成了可控節點,如圖3.2所示。
圖3.2 isAdditionalTaintStep方法的連線作用
如果CodeQL不能自動連線node1和node2節點,就需要手動透過isAdditionalTaintStep來指定連線。
除此之外,在Flow中還有一個方法經常用到isSanitizer,用法如圖3.3所示。這是官方提供的關於log4j2漏洞的查詢指令碼,其中定義了isSanitizer方法來限制flow流中的資料不能是基本資料型別PrimitiveType和BoxedType型別。這是一個特別常用的過濾機制,代表只要是常規的字元型別(Bool、int這些)則不再進行傳遞。
圖3.3 isSanitizer方法的過濾作用
0x04 案例實踐
作為新手來說,要自己編寫有效的CodeQL查詢指令碼是一件很難的事情,幸運的是CodeQL官方為我們提供了大量的demo。
參考地址:https://github.com/github/codeql/tree/main/java/ql/src/experimental/Security/CWE
我們可以直接使用這些demo來完成部分漏洞發現功能。
為了更加清晰的理解關於CodeQL的使用,透過具體案例來演示CodeQL的作用。若依RuoYi是國內使用量較大的後臺管理系統,從網上下載到某版本的RuoYi的原始碼。
1)基於RuoYi的原始碼生成資料庫
arch -x86_64 codeql database create /Users/pang0lin/CodeQL/databases/RuoYi --language=java --command='mvn clean install -DskipTests' --source-root=/Users/pang0lin/cms/若依RuoYi --overwrite
成功生成資料庫之後,會返回類似的success介面,如圖4.1所示。
圖4.1 建立基於RuoYI的資料庫
2)使用官方demo查詢漏洞
官網提供了很多查詢的ql指令碼,其中能直接找到若依相關漏洞的有兩個指令碼,其中第一個指令碼是spel表示式注入的查詢指令碼。
參考地址:https://github.com/github/codeql/blob/main/java/ql/src/experimental/Security/CWE/CWE-094/SpringViewManipulation.ql)
查詢結果如圖4.2所示。
圖4.2 基於SpringViewManipulation的查詢結果
檢視sink點詳情可知這個漏洞是使用者輸入的fragment直接傳入了模版引擎中,如圖4.3所示。
圖4.3 跟蹤sink點之後的結果
這個漏洞其實是屬於若依的一個已知的安全問題,詳情見:https://blog.csdn.net/qq_33608000/article/details/124375219#Thymeleaf_184
雖然在最新版的若依中已經因為升級了thymeleaf版本導致無法利用,但是站在CodeQL的角度還是可以發現這種問題。
另一個可用的CodeQL的查詢指令碼是基於mybatis的SQL隱碼攻擊查詢指令碼,詳情見:https://github.com/github/codeql/blob/main/java/ql/src/experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql
查詢結果如圖4.4所示。
圖4.4 基於MyBatisMapperXmlSqlInjection的查詢結果
可以看到CodeQL找到了若依可能存在的SQL隱碼攻擊漏洞,跟進sink點看一下,如圖所示。每一個都是類似的問題,我們隨便開啟看一個就可以了。
這個可以看到這裡的引數傳遞到SQL語句中,造成了SQL隱碼攻擊漏洞。這個漏洞在網上也有大佬已經提到了漏洞細節資訊,詳情見:
https://juejin.cn/post/7001087308510265352
從上面的兩次查詢中我們可以看到CodeQL在程式碼審計過程中帶來的便利,可以方便的幫助我們定位可能存在的漏洞點。
0x05 結論
CodeQL給我們提供的查詢ql指令碼有很多,如果是透過手工一個一個試的話並不是一個好的解決辦法,並且官方的ql指令碼並不完善,還有很大的完善空間。
如何利用大量的ql指令碼完成自動化的程式碼掃描,我們會在下一篇文章中進行講解。
相關文章
- CodeQL的自動化程式碼審計之路(中篇)2022-11-22
- 基於Python的自動化程式碼審計2018-04-11Python
- 打造自己的php半自動化程式碼審計工具2020-08-19PHP
- 程式碼視覺化的自動化之路2014-09-11視覺化
- 淺談PHP自動化程式碼審計技術2015-04-14PHP
- python自動化審計及實現2020-08-19Python
- CSCMS程式碼審計2022-06-19
- PHP程式碼審計2016-02-19PHP
- buu 程式碼審計2024-06-08
- PHP自動化白盒審計技術與實現2020-08-19PHP
- 自動化的過程(程式設計)2012-07-15程式設計
- 分享一個自研開發的QA自動化審計工具-Sonar檢查2022-01-06
- 程式碼分析引擎 CodeQL 初體驗2019-11-19
- 程式碼簡化之路2017-12-02
- 京喜前端自動化測試之路(小程式篇)2020-07-16前端
- 什麼是程式碼審計?程式碼審計有什麼好處?2024-01-30
- Graudit程式碼安全審計2020-09-16
- 程式碼審計————目錄2018-06-05
- aspx程式碼審計-22017-09-21
- JFinalcms程式碼審計2024-10-14
- 讓你的程式碼自動格式化2018-01-26
- webstorm自動格式化程式碼2020-03-17WebORM
- 哪些業務場景需要做程式碼審計?程式碼審計很重要嗎?2022-01-05
- 程式碼審計是什麼?程式碼審計操作流程分為幾步?2023-03-28
- php程式碼審計之——phpstorm動態除錯2022-04-05PHPORM除錯
- MVC框架的程式碼審計小教程2020-10-29MVC框架
- 自動現代化C++程式碼2019-05-11C++
- 京喜前端自動化測試之路2020-05-12前端
- 淺談倉儲UI自動化之路2023-11-16UI
- 低程式碼如何推動自動化未來2023-03-02
- PHP程式碼審計03之例項化任意物件漏洞2020-10-15PHP物件
- Java反序列化 - CC1鏈 (程式碼審計)2024-10-22Java
- springboot~jpa審計欄位的自動填充2024-05-30Spring Boot
- python 安全編碼&程式碼審計2020-08-19Python
- 合同審查自動化-智慧化尋找合同問題2019-09-06
- PHPFuzzing行動——原始碼審計2017-11-23PHP原始碼
- HR自動化審批流程有哪些好處?2023-03-21
- 原生JavaScript 自動變化的時間日期程式碼2017-03-20JavaScript