CodeQL的自動化程式碼審計之路(中篇)
0x01 前言
在上一篇文章中,我們已經瞭解了關於CodeQL的基本語法,從實際案例角度來體驗了CodeQL在程式碼審計中的作用。從這篇文章開始,我們將開始真正打造基於CodeQL的自動化程式碼審計工具,由於這僅僅是來自於個人興趣的研究,並非來自成熟專案,所以在文章中可能缺陷,各位大佬如果有更好的意見建議,請私信。
CodeQL的程式碼審計整體過程可以分成兩個部分,如圖1.1所示,分別是從原始碼解析成CodeQL資料庫和從資料庫中查詢出安全隱患。本次分享主要關注第二階段的內容,假設我們已經有了CodeQL資料庫之後,下一步基於資料庫的自動化查詢。
圖1.1 CodeQL程式碼審計過程階段
為什麼我們最終結果得到的是安全隱患,而不是漏洞呢?這是因為CodeQL並不是萬能的,它只能幫我們找到可能的安全隱患,而不能一定確認漏洞存在,這在後面的文章中會說到原因。
當前的自動化程式碼審計工具將採用python3開發,針對的目標語言是java,後續如果有時間,也會陸續支撐其他語言。目前程式碼還不是特別完善,後期後繼續對程式碼進行最佳化,見github地址:https://github.com/webraybtl/codeql
0x02 工具設計
基於CodeQL的自動化程式碼審計工具流程其實和傳統的漏洞掃描工具相似,所以我們還是按照傳統漏掃的思路來設計工具。關於第一階段原始碼轉化為資料庫的部分在下一篇文章來詳述,這裡還是隻關注第二階段資料庫查詢的內容,如圖2.1所示。
圖2.1 自動化工具設計流程
其實從流程中可以看出,工具的主要功能是基於ql外掛的遍歷,對外掛結果的格式化輸出。首先需要解決的問題是關於ql外掛來源的問題,在上一篇文章中,我們有提到CodeQL官方給我們提供了很多測試用的demo例項https://github.com/github/codeql/tree/main/java/ql/src/experimental/Security/CWE。
官方按照CWE提供了多個不同型別的ql外掛,部分外掛是可以直接來用的,但是有的外掛涉及到自定義qll庫,需要進行一定的轉化才能使用,如圖2.2所以,FilePathInjection.ql指令碼就是典型的有自定義庫的指令碼。
圖2.2 使用了qll自定義庫的ql指令碼
在我們設計的自動化工具中,為了方便會只查詢單個ql指令碼,需要把ql指令碼中呼叫的qll庫進行轉化。轉化的方式是顯示的把qll庫中定義的類和謂詞直接定義到ql指令碼中,我已經把官方提供的全部指令碼都轉化了一遍,後續會將完整的程式碼分享到github。
為了方便統一的對結果進行格式化輸出,我們期待每一個ql檔案最終返回的結果都是統一格式,所以還需要對每個ql檔案最終的返回結果進行約束,典型的demo如下所示。其中select後面的值是ql指令碼最終返回的資料。
from DataFlow::PathNode source, DataFlow::PathNode sink, BeanShellInjectionConfig conf
where conf.hasFlowPath(source, sink)
select source.toString(),source.getNode().getEnclosingCallable(),source.getNode().getEnclosingCallable().getFile().getAbsolutePath(),
sink.toString(),source.getNode().getEnclosingCallable(), sink.getNode().getEnclosingCallable().getFile().getAbsolutePath(), "BeanShell injection"
表2.1 ql指令碼輸出規範約束
由於CodeQL官方並不對引擎開源,我們只能直接使用官方編譯好的版本,官方編譯好的引擎並不支援python這些語言,只能從命令列進行呼叫,如圖2.3所示。其中-d引數用於表示待查詢的資料庫路徑,最後跟的是要查詢的ql指令碼路徑。
圖2.3 透過命令列呼叫codeql查詢
由於CodeQL每次查詢都需要使用ql指令碼檔案路徑,如果每次查詢都需要先生成一個檔案,然後查詢結束之後再刪除檔案,程式碼顯得怪怪的。好在python給我們提供了tempfile庫,可以稍微優雅的解決這個問題,如圖2.4所示。這是一段我專案中檢查環境是否準備好了的程式碼,透過tempfile生成臨時的ql指令碼,臨時指令碼在執行結束之後會自動自動刪除。
圖2.4 使用tempfile生成臨時檔案查詢codeql
本來想自己封裝了一個類來呼叫呼叫CodeQL執行,但是突然看到網上已經有大佬寫好了一個相應的類https://github.com/AlexAltea/codeql-python,其實現思路和我之前的想法差不多,本質上還是從命令列呼叫的CodeQL。然而我直接執行大佬的程式碼卻執行不成功,主要原因還是在於生成的臨時檔案必須要在ql sdk所在測試路徑,路徑下必須有正確配置的qlpack.yml檔案。所以我在原始碼的基礎上修改了一下,主要是固定sdk路徑為配置好的路徑。
這之後我們一個簡易的基於CodeQL的自動化程式碼審計工具雛型就差不多了,後續會陸續在這個框架的基礎上最佳化功能。
0x03 外掛最佳化
官方雖然提供了大約59個java的ql查詢外掛,但是實際上還遠不能滿足我們的需求,我們希望有更多的白帽子參與進來提供更多的ql查詢外掛。當前階段,我按照自己日常漏洞挖掘過程補充一些ql查詢外掛,如下所示,相關外掛均在plugins/java_ext目錄。
表3.1 新增的Java常見漏洞查詢ql指令碼
本次新增只是一個開端,並不能覆蓋全部,自知還相差很遠。但是不斷的最佳化,總歸會有好的效果。由於部分小夥伴對與CodeQL的語法瞭解甚少,我們用一個簡單的指令碼Unserialze.ql來說明完整的CodeQL指令碼的寫法。
反序列化漏洞是java中常見的漏洞,典型的漏洞程式碼寫法如下。這是一段從某應用中提取的真實漏洞的部分程式碼。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
ObjectInputStream objin = new ObjectInputStream(request.getInputStream()); //這裡是獲取使用者輸入
response.setContentType("application/x-download");
ServletOutputStream out = response.getOutputStream();
try {
String dsName = (String)objin.readObject(); //這裡是反序列化的點
System.out.println(dsName);
} catch (Exception var11) {
var11.printStackTrace();
}
out.close();
objin.close();}
其中最關鍵的是使用者可控的source點為request.getInputStream(),最後的危險操作sink點為objin.readObject()。也就是說外部傳入的postdata直接進行了反序列化操作,則可能導致反序列化漏洞。對於CodeQL中,可以編寫對應的查詢指令碼如下。
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow
class UnserializeSink extends DataFlow::Node {
UnserializeSink(){
exists(MethodAccess ma,Class c | ma.getMethod().hasName("readObject") and
ma.getQualifier().getType() = c and
c.getASupertype*().hasQualifiedName("java.io", "InputStream") and
this.asExpr() = ma
)
}}
class UnserializeSanitizer extends DataFlow::Node {
UnserializeSanitizer() {
this.getType() instanceof BoxedType or this.getType() instanceof PrimitiveType
}
}
class JavaUnserialize extends TaintTracking::Configuration {
JavaUnserialize() { this = "Java Unsearialize" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnserializeSink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof UnserializeSanitizer }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, JavaUnserialize confwhere conf.hasFlowPath(source, sink)select source.toString(),source.getNode().getEnclosingCallable(),source.getNode().getEnclosingCallable().getFile().getAbsolutePath(),
sink.toString(),source.getNode().getEnclosingCallable(), sink.getNode().getEnclosingCallable().getFile().getAbsolutePath(), "Potential JAVA Unserialize Vulnerability"
其中source點直接使用CodeQL預定義的類RemoteFlowSource,而sink點則是透過下面的程式碼實現。判斷的邏輯是存在一個方法名為readObject的呼叫,並且呼叫的主體繼承自java.io.InputStream類。注意這些說的是反序列化漏洞,不涉及利用鏈,不一定就真的能導致RCE效果。
exists(MethodAccess ma,Class c | ma.getMethod().hasName("readObject") and ma.getQualifier().getType() = c and c.getASupertype*().hasQualifiedName("java.io", "InputStream") and this.asExpr() = ma )
單獨執行對應的指令碼,則可以發現程式中可能存在的反序列化漏洞,如圖3.1所示。
圖3.1 單獨執行Unserialize.ql指令碼的效果
其他指令碼就不再依次講解,如果有小夥伴感興趣,非常期待小夥伴能為我們提供外掛。如果小夥伴不知道怎麼編寫CodeQL指令碼,可以把有漏洞的程式碼邏輯給私信我,由我來轉化為CodeQL外掛。
0x04 工具使用
回到工具本身,當前完整的程式碼我已經放在github,使用方式如下所示。
使用之前應該首先安裝CodeQL,並配置config/config.ini,其中最關鍵的是配置臨時生成的ql指令碼儲存的路徑qlpath,如圖4.1所示。確保qlpath當前目錄下面有配置檔案qlpack.yml。如果使用的過程中有問題,建議把debug配置為on。
圖4.1 專案配置檔案
執行python3 main.py -h,如圖4.2所示。
其中引數-d代表資料庫檔案地址,必填。
引數-s代表是否跳過環境檢查,不填預設為false,首次使用建議不跳過環境檢測。
圖4.2 專案支援的引數列表
執行python3 main.py -d /Users/xxxx/CodeQL/databases/RuoYI/,透過若依原始碼來掩飾效果。
圖4.3 使用工具獲取的掃描結果
最終的掃描結果是以csv檔案儲存在out/result/目錄,開啟相應的結果,如圖4.4所示。
圖4.4 對結果進行格式化輸出到CSV檔案中
關於結果的分析我們在上一篇文章中已經涉及到一些,這裡就不再分析結論。
0x05 工具不足
在圖4.4的結果中,有很多FilePathInjection外掛掃描的結果,其中記過都很相似,以其中之一為例,我們基於檔案的source和sink定位其中的問題。
定位到source檔案和方法,com.ruoyi.web.controller.system. SysProfileController類的updateAvatar方法,如圖5.1所示。
圖5.1 Source類與方法
繼續跟蹤upload方法,就可以到sink點,如圖5.2所示。
圖5.2 Sink的類與方法
這裡其實RuoYI已經對上傳檔案的副檔名進行了限制,然後CodeQL仍然把這裡識別為漏洞,這是典型的誤報行為,而這也是CodeQL的程式碼審計工具中最難解決的一個問題。
CodeQL可以跟蹤Source和Sink流,但是畢竟仍然只是靜態程式碼審計工具,無法自動解析程式碼中的一些過濾操作,導致可能會出現誤報。而這也是文章開頭提到的CodeQL只能作為輔助工具發現安全隱患,不能確定是否一定存在漏洞的原因。
0x06 結論
距離自動化的程式碼審計工具,我們仍然有很長的路要走,如果小夥伴能提供一些可用的ql外掛或者提供有漏洞的程式碼樣本由我們來編寫ql外掛,我們都將非常感激。
後續我們會陸續豐富工具的功能,特別是解決前一階段生成資料庫的問題。
相關文章
- CodeQL的自動化程式碼審計之路(上篇)2022-11-14
- 基於Python的自動化程式碼審計2018-04-11Python
- 打造自己的php半自動化程式碼審計工具2020-08-19PHP
- python自動化審計及實現2020-08-19Python
- 程式碼分析引擎 CodeQL 初體驗2019-11-19
- buu 程式碼審計2024-06-08
- JFinalcms程式碼審計2024-10-14
- CSCMS程式碼審計2022-06-19
- 什麼是程式碼審計?程式碼審計有什麼好處?2024-01-30
- 程式碼審計————目錄2018-06-05
- Graudit程式碼安全審計2020-09-16
- php程式碼審計之——phpstorm動態除錯2022-04-05PHPORM除錯
- PHP自動化白盒審計技術與實現2020-08-19PHP
- 分享一個自研開發的QA自動化審計工具-Sonar檢查2022-01-06
- 程式碼審計是什麼?程式碼審計操作流程分為幾步?2023-03-28
- springboot~jpa審計欄位的自動填充2024-05-30Spring Boot
- 京喜前端自動化測試之路(小程式篇)2020-07-16前端
- python 安全編碼&程式碼審計2020-08-19Python
- webstorm自動格式化程式碼2020-03-17WebORM
- 哪些業務場景需要做程式碼審計?程式碼審計很重要嗎?2022-01-05
- MVC框架的程式碼審計小教程2020-10-29MVC框架
- oasys系統程式碼審計2024-11-06
- PHP程式碼審計03之例項化任意物件漏洞2020-10-15PHP物件
- Java反序列化 - CC1鏈 (程式碼審計)2024-10-22Java
- 自動化測試成神之路2020-07-28
- 前端開發常用程式碼片段(中篇)2018-05-09前端
- 自動現代化C++程式碼2019-05-11C++
- 某個OA系統的程式碼審計2024-07-24
- 快刀斬亂麻,DevOps讓程式碼評審也自動起來2024-04-23dev
- 記一次完整的PHP程式碼審計——yccms v3.4審計2023-02-21PHP
- 低程式碼如何推動自動化未來2023-03-02
- Java程式碼審計入門篇2018-06-28Java
- 程式碼審計入門總結2020-08-19
- Hackthebox bagel.dll 程式碼審計2024-07-02
- [JavaWeb]Shiro漏洞集合——程式碼審計2022-01-24JavaWeb
- 為什麼要做程式碼審計?2021-12-29
- Java 程式碼審計 — 1. ClassLoader2021-11-27Java
- 如何做人性化的程式碼審查?2018-05-30