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中,添加環境變量。
使用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命令來創建查詢數據庫,命令如下所示。
在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是使用下面的命令。
安裝好了之后就可以使用下面的命令來生成數據庫,與傳統方式不同的是需要在命令前面增加arch -x86_64,如圖2.5所示。
圖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代表獲取類對應的全限定類名。
Demo2: 查詢所有字段Field,滿足條件是字段類型是public,并且字段類型繼承java.lang.Throwable。(Fastjson1.2.80漏洞利用鏈的查找方式)。
其中getASupertype代表獲取類對應的父類,*代表遞歸查找所有父類。
getDeclaringType代表獲取字段對應的定義類型。
getAModifier代表獲取字段對應的修飾符。
2) 與Access相關的概念
access代表對變量或者方法的調用,主要有VarAccess和MethodAccess。
Demo1:查詢所有繼承自java.util.list的變量及變量的引用。
Demo2:查詢所有InputStream類對應的readObject方法調用(遍歷反序列化漏洞的基礎)。
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的源碼生成數據庫
成功生成數據庫之后,會返回類似的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點看一下,如圖4.5所示。每一個都是類似的問題,我們隨便打開看一個就可以了。
這個可以看到這里的參數傳遞到SQL語句中,造成了SQL注入漏洞。這個漏洞在網上也有大佬已經提到了漏洞細節信息,詳情見:
https://juejin.cn/post/7001087308510265352
從上面的兩次查詢中我們可以看到CodeQL在代碼審計過程中帶來的便利,可以方便的幫助我們定位可能存在的漏洞點。
0x05 結論
CodeQL給我們提供的查詢ql腳本有很多,如果是通過手工一個一個試的話并不是一個好的解決辦法,并且官方的ql腳本并不完善,還有很大的完善空間。
如何利用大量的ql腳本完成自動化的代碼掃描,我們會在下一篇文章中進行講解。
本文作者:盛邦安全WebRAY, 轉載請注明來自FreeBuf.COM