CodeQL的自動(dòng)化代碼審計(jì)之路
0x01 前言
最近關(guān)于CodeQL的概念很火,大家普遍認(rèn)為這會(huì)是下一代的代碼審計(jì)神器。網(wǎng)上關(guān)于CodeQL的文章已經(jīng)有不少,但是多數(shù)文章還是在分析CodeQL的安裝和簡單使用用例。真正使用CodeQL來進(jìn)行自動(dòng)化代碼審計(jì)的文章較少,本文主要研究基于CodeQL實(shí)現(xiàn)全自動(dòng)的代碼審計(jì)工具實(shí)現(xiàn)思路,預(yù)計(jì)文章分成三部分完成,目前是第一部分內(nèi)容。
CodeQL(全稱Code Query Language),從其英文名稱中可以看出這是一種基于代碼的查詢語言,其作用主要是通過編寫好的語句查詢代碼中可能存在的安全隱患。學(xué)習(xí)CodeQL類似于學(xué)習(xí)一門全新的編程語言,語法類似于SQL,但是比傳統(tǒng)SQL還是要難很多。目前CodeQL支持對多種語言,包括java、javascript、go、python、C、Csharp等,但是很遺憾的是不支持“世界上最好的語言”PHP。這大概是因?yàn)镻HP實(shí)在是太靈活了,函數(shù)名是字符串變量這種調(diào)用方式確實(shí)很難從AST語法樹中靜態(tài)分析出問題,但這并不能阻礙我們學(xué)習(xí)CodeQL的興趣。文章所有內(nèi)容基本上圍繞java語言展開,其他語言操作基本類似。
0x02 環(huán)境準(zhǔn)備
網(wǎng)上關(guān)于CodeQL安裝的文章已經(jīng)很多了,本來不打算再說這個(gè)事情,但是因?yàn)楸救嗽贑odeQL安裝過程中遇到不兼容mac m1架構(gòu)的情況,我想還有很多小伙伴也會(huì)遇到這個(gè)問題的,這里主要以MAC的環(huán)境來說明安裝過程。
CodeQL的安裝主要分成引擎和SDK,新建一個(gè)目錄CodeQL(~/CodeQL/)來保存后續(xù)所有的相關(guān)的工具和代碼。
首先下載最新的引擎包,下載地址是:https://github.com/github/codeql-cli-binaries/releases
下載之后解壓把codeql文件夾放在剛才新建的文件夾CodeQL中,添加環(huán)境變量。
使用source命令是環(huán)境變量生效,然后命令行中運(yùn)行codeql,如圖2.1所示。
圖2.1 CodeQL引擎安裝
然后需要下載CodeQL對應(yīng)的sdk包,下載地址是:
https://github.com/Semmle/ql
下載之后也需要把ql文件夾復(fù)制到~/CodeQL文件夾中。
在CodeQL文件夾中新建databases文件夾,用于存放后續(xù)使用codeql生成的數(shù)據(jù)庫,那么一切準(zhǔn)備好了之后我們的CodeQL目錄之下就會(huì)是三個(gè)文件夾,如圖2.2所示。
圖2.2 CodeQL安裝
后續(xù)我們就可以使用codeql database create命令來創(chuàng)建查詢數(shù)據(jù)庫,命令如下所示。
在windows環(huán)境中和以前的mac環(huán)境中確實(shí)沒有問題,但是如果是在m1的環(huán)境中會(huì)報(bào)錯(cuò),報(bào)錯(cuò)信息如圖2.3所示。錯(cuò)誤的原因是codeql官方提供的工具是x86架構(gòu)的,不能直接在arm中使用。
圖2.3 在MAC M1環(huán)境中codeql運(yùn)行錯(cuò)誤
從官網(wǎng)中找到了codeql對m1的支持情況,如圖2.4所示。從圖中可以明確看出codeql確實(shí)是支持m1架構(gòu)的,但是需要依賴rosetta2和xcode。但是并沒有給出具體的安裝和使用步驟,必須吐槽官方一點(diǎn)也不人性化,說話說一半。
圖2.4 CodeQL支持M1架構(gòu)
后來慢慢摸索著裝xcode和rosetta2,安裝xcode是直接通過appstore來裝的,安裝rosetta2是使用下面的命令。
安裝好了之后就可以使用下面的命令來生成數(shù)據(jù)庫,與傳統(tǒng)方式不同的是需要在命令前面增加arch -x86_64,如圖2.5所示。
圖2.5 在M1中使用codeql生成數(shù)據(jù)庫
0x03 語法基礎(chǔ)
CodeQL是一門全新的語言,基礎(chǔ)的CodeQL語法網(wǎng)上已經(jīng)有很多文章。大家在學(xué)習(xí)之前可以首先參考鏈接,了解關(guān)于CodeQL的基礎(chǔ)語法,重點(diǎn)掌握關(guān)于類和謂詞的概念。
參考鏈接: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/
直接來學(xué)習(xí)語法是一件很枯燥的事情,我們這里只是總結(jié)一些CodeQL中重點(diǎn)的概念。關(guān)于語法詳情在后續(xù)的實(shí)際案例分析中會(huì)有更深刻的體會(huì)。
1) 與Class相關(guān)的概念
與類直接相關(guān)的概念包括Class、Method、Field、Constructor,其代表的意義與java語言一致,通過其相互組合可以從數(shù)據(jù)庫中篩選出符合條件的類和方法。
Demo1: 查詢類的全限定名中包含Person的類,其中方法getQualifiedName代表獲取類對應(yīng)的全限定類名。
Demo2: 查詢所有字段Field,滿足條件是字段類型是public,并且字段類型繼承java.lang.Throwable。(Fastjson1.2.80漏洞利用鏈的查找方式)。
其中g(shù)etASupertype代表獲取類對應(yīng)的父類,*代表遞歸查找所有父類。
getDeclaringType代表獲取字段對應(yīng)的定義類型。
getAModifier代表獲取字段對應(yīng)的修飾符。
2) 與Access相關(guān)的概念
access代表對變量或者方法的調(diào)用,主要有VarAccess和MethodAccess。
Demo1:查詢所有繼承自java.util.list的變量及變量的引用。
Demo2:查詢所有InputStream類對應(yīng)的readObject方法調(diào)用(遍歷反序列化漏洞的基礎(chǔ))。
3)與Type相關(guān)的概念
Type代表類型,是屬于CodeQL中一個(gè)很重要的概念,Type類有倆個(gè)直接派生類PrimitiveType,RefType。
PrimitiveType代表Java中的基礎(chǔ)數(shù)據(jù)類型,派生類有boolean, byte, char, double, float, int, long, short, void,, null。
RefType代表Java中的引用類型,有派生類Class、Interface、EnumType、Array。
Type多數(shù)情況下是和Acess相互使用的,其實(shí)在上面Acess的例子中幾乎都用到了Type相關(guān)的類。
4)與Flow相關(guān)的概念
Flow是CodeQL中最重要的概念,代表數(shù)據(jù)流,與此對應(yīng)的概念包括source和sink。
source代表可控的用戶輸入點(diǎn),通常是指WEB站點(diǎn)中的URL中參數(shù),例如
request.getParameter("name")。其他例如命令行參數(shù)args也屬于source。在CodeQL中已經(jīng)存在RemoteFlowSource類,在類中已經(jīng)定義了很多常見的source點(diǎn),可以滿足我們做一般性代碼審計(jì)的需要。但是如果我們是要做特定jar包漏洞挖掘,例如復(fù)現(xiàn)log4j2的遠(yuǎn)程命令執(zhí)行漏洞,由于log4j2包中不包含常規(guī)的source點(diǎn),就需要用戶自定義source。
sink代表危險(xiǎn)的函數(shù),通常是指一些危險(xiǎn)的操作,包括命令執(zhí)行、代碼執(zhí)行、jndi注入、SQL注入、XML注入等。CodeQL雖然也預(yù)置了部分的sink點(diǎn),但是遠(yuǎn)不能滿足實(shí)際的需求,需要我們在不同的漏洞環(huán)境中自定義sink點(diǎn)。
在有了source和sink之后我們可以基于CodeQL提供的查詢機(jī)制,自動(dòng)判斷是否存在flow可以連接source和sink,一個(gè)典型的用法如下,如圖3.1所示。
圖3.1 典型的flow利用方式
在圖3.1所示的Flow中,自定義類繼承自TaintTracking::Configuration,并且覆蓋其中的isSource個(gè)isSink方法。這個(gè)是固定寫法,后續(xù)的絕大部分的ql腳本都包含這樣的代碼。
其中isAdditionalTaintStep方法是CodeQL的類TaintTracking::Configuration提供的的方法,它的原型是:override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {}。它的作用是將一個(gè)可控節(jié)點(diǎn)A強(qiáng)制傳遞給另外一個(gè)節(jié)點(diǎn)B,那么節(jié)點(diǎn)B也就成了可控節(jié)點(diǎn),如圖3.2所示。
圖3.2 isAdditionalTaintStep方法的連接作用
如果CodeQL不能自動(dòng)連接node1和node2節(jié)點(diǎn),就需要手動(dòng)通過isAdditionalTaintStep來指定連接。
除此之外,在Flow中還有一個(gè)方法經(jīng)常用到isSanitizer,用法如圖3.3所示。這是官方提供的關(guān)于log4j2漏洞的查詢腳本,其中定義了isSanitizer方法來限制flow流中的數(shù)據(jù)不能是基本數(shù)據(jù)類型PrimitiveType和BoxedType類型。這是一個(gè)特別常用的過濾機(jī)制,代表只要是常規(guī)的字符類型(Bool、int這些)則不再進(jìn)行傳遞。
圖3.3 isSanitizer方法的過濾作用
0x04 案例實(shí)踐
作為新手來說,要自己編寫有效的CodeQL查詢腳本是一件很難的事情,幸運(yùn)的是CodeQL官方為我們提供了大量的demo。
參考地址:https://github.com/github/codeql/tree/main/java/ql/src/experimental/Security/CWE
我們可以直接使用這些demo來完成部分漏洞發(fā)現(xiàn)功能。
為了更加清晰的理解關(guān)于CodeQL的使用,通過具體案例來演示CodeQL的作用。若依RuoYi是國內(nèi)使用量較大的后臺(tái)管理系統(tǒng),從網(wǎng)上下載到某版本的RuoYi的源碼。
1)基于RuoYi的源碼生成數(shù)據(jù)庫
成功生成數(shù)據(jù)庫之后,會(huì)返回類似的success界面,如圖4.1所示。
圖4.1 創(chuàng)建基于RuoYI的數(shù)據(jù)庫
2)使用官方demo查詢漏洞
官網(wǎng)提供了很多查詢的ql腳本,其中能直接找到若依相關(guān)漏洞的有兩個(gè)腳本,其中第一個(gè)腳本是spel表達(dá)式注入的查詢腳本。
參考地址:https://github.com/github/codeql/blob/main/java/ql/src/experimental/Security/CWE/CWE-094/SpringViewManipulation.ql)
查詢結(jié)果如圖4.2所示。
圖4.2 基于SpringViewManipulation的查詢結(jié)果
查看sink點(diǎn)詳情可知這個(gè)漏洞是用戶輸入的fragment直接傳入了模版引擎中,如圖4.3所示。
圖4.3 跟蹤sink點(diǎn)之后的結(jié)果
這個(gè)漏洞其實(shí)是屬于若依的一個(gè)已知的安全問題,詳情見:https://blog.csdn.net/qq_33608000/article/details/124375219#Thymeleaf_184
雖然在最新版的若依中已經(jīng)因?yàn)樯?jí)了thymeleaf版本導(dǎo)致無法利用,但是站在CodeQL的角度還是可以發(fā)現(xiàn)這種問題。
另一個(gè)可用的CodeQL的查詢腳本是基于mybatis的SQL注入查詢腳本,詳情見:https://github.com/github/codeql/blob/main/java/ql/src/experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql
查詢結(jié)果如圖4.4所示。
圖4.4 基于MyBatisMapperXmlSqlInjection的查詢結(jié)果
可以看到CodeQL找到了若依可能存在的SQL注入漏洞,跟進(jìn)sink點(diǎn)看一下,如圖4.5所示。每一個(gè)都是類似的問題,我們隨便打開看一個(gè)就可以了。
這個(gè)可以看到這里的參數(shù)傳遞到SQL語句中,造成了SQL注入漏洞。這個(gè)漏洞在網(wǎng)上也有大佬已經(jīng)提到了漏洞細(xì)節(jié)信息,詳情見:
https://juejin.cn/post/7001087308510265352
從上面的兩次查詢中我們可以看到CodeQL在代碼審計(jì)過程中帶來的便利,可以方便的幫助我們定位可能存在的漏洞點(diǎn)。
0x05 結(jié)論
CodeQL給我們提供的查詢ql腳本有很多,如果是通過手工一個(gè)一個(gè)試的話并不是一個(gè)好的解決辦法,并且官方的ql腳本并不完善,還有很大的完善空間。
如何利用大量的ql腳本完成自動(dòng)化的代碼掃描,我們會(huì)在下一篇文章中進(jìn)行講解。
本文作者:盛邦安全WebRAY, 轉(zhuǎn)載請注明來自FreeBuf.COM