
0x01 前言
(資料圖片)
在上一篇文章中,我們基于CodeQL官方提供的sdk實現了自動化查詢數據庫功能,在文章中也提到實現完整的自動化代碼審計還缺少“數據庫生成”相應的功能,本文主要針對“數據庫生成”這一階段來闡述整個過程實踐中的優缺點。
生成數據庫是整個CodeQL使用中最重要的一個步驟,對于java語言來說,生成數據庫的過程要比其他語言更難。CodeQL的數據庫中本質上保存的是與代碼相關的AST語法樹,通過VSCode提供的AST Viewer功能,可以很清晰地看出來最終生成地AST結果,如圖1.1所示。熟悉詞法和語法分析的小伙伴相信不會對AST的結構感到陌生。
圖1.1 使用CodeQL生成的AST語法樹
熟悉JAVA安全的小伙伴可能聽過另一種生成AST語法樹的方法GadgetInspector。如果單純只是針對JAVA語言的話,個人覺得GadgetInspector的實現思路和效果是要優于CodeQL的,主要對比如表1.1所示。
表1.1 CodeQL和GadgetInspector對比
個人一直研究基于CodeQL的代碼審計工具,主要是想解決多種語言的代碼審計問題,雖然目前的嚴重重點還是在java語言中,但是對其他語言的兼容會是后續的研究重點。
0x02 初探
對于腳本語言(例如python)CodeQL在生成數據庫的時候是很簡單的,命令如下所以,注意對于腳本語言不應使用--command參數。非ARM系統去除“arch -x86_64”,后續所有命令均按此處理,不再贅述。
arch -x86_64 codeql database create /Users/xxx/CodeQL/databases/giza --language=python --source-root=/Users/xxx/Downloads/giza
在成功生成數據庫之后一般會有successfully的提示,如圖2.1所示。一般腳本語言創建數據庫的過程都很簡單,不容易出現問題。
圖2.1 使用CodeQL創建python語言對應的數據庫
由于java是一種編譯型語言,創建的過程中需要指定編譯命令。對于源碼文件來說,典型的java語言創建數據庫的命令如下所示,結果如圖2.2所示。
arch -x86_64 codeql database create /Users/test/CodeQL/databases/mvn_test --language=java --command="mvn clean install -DskipTests" --source-root=/Users/pang0lin/java/projects/mvn_test --overwrite
--language: 指定對應源碼的語言類型
--command: 執行的編譯命令,對于編譯型語言,此參數必填
--source-root: 指定源碼路徑
--overwrite: 覆蓋保存生成的數據庫,如果當前數據庫以存在,則必填
圖2.2 使用CodeQL創建java語言對應的數據庫
上面的過程中幾乎屬于是CodeQL官方給出的關于創建數據庫的典型案例,但是在現實環境中確不一定能直接拿到目標編譯前的源碼,一般情況下可以獲取到的源碼都是編譯后的源碼,主要表現形式如下。
1) 用于部署SpringBoot項目的jar包或者用于部署tomcat項目的war包。
2) 直接從目標WEB目錄拷貝的源碼文件,一般包含jsp文件、class文件、配置文件等。
在開始我們的探索之前,首先需要明確一個觀點是,使用原生的CodeQL創建數據庫的指令不會創建jsp文件對應的AST語法樹。為了演示這個現象,我們創建一個SpringMVC的項目,并且在webapp目錄中增加一個jsp文件,如圖2.3所示。
圖2.3 對比SpringMVC生成數據庫中的文件和源代碼中的區別
從圖2.3中可以看出,對于SpringMVC類型的源代碼,如果直接采用CodeQL官方demo的方式來生成數據庫,并不會創建jsp文件對應的AST語法樹。所以在圖2.3右邊生成的數據庫中沒有找到shell.jsp和index.jsp對應的文件。
如果需要了解詳細的關于CodeQL創建數據庫的原理,可以參考大佬的文章https://paper.seebug.org/1921/。
從大佬的文章中可以得出一個結論,如果需要CodeQL創建對應的數據庫,則該文件必須要有“編譯”過程。所以在實際環境中,不論是jsp文件、class文件,還第三方的jar包,都是不能直接被創建到數據庫中的。
如果要用編譯后的文件來生成數據庫,則必須首先對把對應的文件反編譯成java源文件,然后再對java源文件進行“編譯”才能成功生成對應文件對應的數據庫。
0x03 研究
從上面的分析中,我們首先需要明確的一個事情,就是需要對編譯后的源碼進行反編譯。目前市面上關于java反編譯的工具有很多,但是作為idea的忠實粉絲,最初的選擇還是直接使用idea自帶的反編譯工具java-decompiler.jar。
安裝了idea之后都存在java-decompiler.jar文件,默認位置是/Applications/IntelliJ IDEA.app/Contents/plugins/java-decompiler/lib/java-decompiler.jar。直接拷貝對應的jar包,源于java-decompiler.jar的用法如下:
java -cp java-decompiler.jar org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true test/ xxx/
圖3.1 使用java-decompiler.jar對目標進行反編譯
但是在后面實際的利用過程中發現有大量的文件在反編譯之后沒法再進行編譯,如圖3.2所示,這是使用idea進行反編譯之后得到的部分代碼,在代碼中可以很清晰的看到重復定義了變量_hashCode。所以再對這個代碼進行編譯的時候就會報錯。
圖3.2 Idea反編譯時遇到的重復定義變量的問題
對上面的代碼換一個反編譯工具,使用jd-gui來對同樣的代碼進行反編譯,得到的代碼如圖3.3所示。
圖3.3 使用jd-gui來反編譯代碼
從代碼中可以明顯的看出jd-gui并不會出現重復定義變量的問題,類似的問題還有int i=False;這種,如圖3.4所示,在IDEA反編譯的代碼中會出現類型不匹配的問題,但是jd-gui并不會。所以,經過實踐之后,我最終拋棄了idea的java-decompiler.jar,轉而開始使用jd-gui。但是jd-gui是屬于界面版本工具,我們需要使用其命令行版本jd-cli。
圖3.4 由于類型不匹配導致的編譯錯誤
下一個要解決的問題是從jsp文件轉java文件,熟悉java的小伙伴應該都很了解tomcat的運行機制,在tomcat解析jsp文件的過程中,會首先通過tomcat-jasper.jar包來把jsp轉化為對應的java類文件,如圖3.5所示。
圖3.5 把shell.jsp文件轉化為shell_jsp.java文件
從圖3.5可以看出shell.jsp文件已經被jsper轉化為shell_jsp.java類文件,此文件是可以被“編譯”的。那么我們后續如果要通過CodeQL來生成jsp文件對應的數據庫,本質上還是生成的類似于shell_jsp.java文件對應的數據庫。
那么,如何利用tomcat-jasper.jar來把jsp轉化為java?其實只需要一小段代碼,如圖3.6所示。
圖3.6 通過JspC類把jsp文件轉化為java類文件
為了能夠直接在python的代碼中調用對應的java代碼,我把java代碼打包成可以直接在命令行中調用的jar包jsp2class.jar(相關文件見源碼),使用如圖3.7所示。
第一個參數:jsp文件所在網站的跟目錄。
第二個參數:jsp文件全路徑名,相對于網站跟目錄的相對路徑。
第三個參數:生成的java文件保存的路徑。
圖3.7 使用打包好的jsp2class文件來轉化jsp文件
目前我們已經解決了反編譯需要的問題,剩下就是編譯需要的問題。CodeQL給出的官方demo中是通過maven來觸發代碼的編譯過程,但是實際環境中并不是所有的目標環境都是maven,參考大佬https://paper.seebug.org/1921/文章中提到的方式,可以采用ecj.jar來對目標代碼進行編譯。
使用ecj.jar與傳統使用javac的方式對目標進行編譯有容錯率更高的優點,javac在編譯的過程中遇到錯誤會報錯并退出,但是ecj存在忽略錯誤并繼續編譯的選項。通過反編譯方式得到的源碼在編譯過程中遇到錯誤是很正常的現象,而ecj可忽略錯誤的特性很好的滿足了CodeQL生成數據庫的要求,對于CodeQL而言更需要的是編譯過程能更全的覆蓋源代碼文件,而不是編譯之后的結果要可運行。
ecj.jar要求jdk1.8的環境運行,完整的ecj支持的參數可以參考官方文檔https://www.ibm.com/docs/zh/radfws/9.6?topic=SSRTLW_9.6.0/org.eclipse.jdt.doc.user/tasks/task-using_batch_compiler.html。如圖3.8所示。
圖3.8 關于ecj.jar支持的參數列表
詳細了解ecj.jar支持的參數列表對于我們后期優化ecj編譯過程至關重要,因為后面很容易出現的一個情況就是存在的源碼在CodeQL數據庫中沒有生成。典型的運行方式如下所示。
java -jar ecj-4.6.1.jar -extdirs /Users/xxx/lib -encoding UTF-8 -8 -warn:none -proceedOnError -noExit @/Users/xxx/test/CodeQLpy/file.txt
-proceedOnError:指定ecj忽略錯誤繼續運行,這對于CodeQL生成數據庫很重要。
-warn:none:忽略所有警告,一般來說警告太多了,沒有什么意義。
-extdirs:指定ecj編譯過程中依賴的外部jar包目錄。
@/Users/xxx/test/CodeQLpy/file.txt::指定要編譯的目標java文件列表。
把上面編譯的命令保存到sh文件中,然后就可以通過下面的命令來生成數據庫,如圖3.9所示。
arch -x86_64 codeql database create /Users/xxx/CodeQL/databases/test1 --language=java --command="sh /Users/xxx/Downloads/test/1.sh"
圖3.9 通過ecj編譯過程生成數據庫
0x04 工具
我的初衷是基于CodeQL來達到半自動化代碼審計的效果,通過python語言來實現整個工具的流程,雖然目前流程中還有很多已知的不足,但是基本的雛形已經有了,后面就是不斷對代碼進行優化的過程。
目前已經支持的功能如表4.1所示。
表4.1 項目支持的功能列表
項目運行時截圖如圖4.1所示。
圖4.1 程序支持的參數列表
-d: 指定要進行掃描的數據庫,-d和-t參數二選一出現。
-t: 指定目標源碼,可以是jar包,war包,文件夾或者maven源代碼。-d和-t參數二選一出現。
-c: 指定源碼是屬于編譯前的源碼還是編譯后的源碼,默認是屬于編譯前源碼。
-s: 指定是否跳過環境檢查。
注意,如果是-t指定的源碼是屬于文件夾類型,則要求文件夾必須是網站跟路徑。如果不是跟路徑會出現相對路徑錯誤導致的異常。
本地運行需要依據實際情況配置config/config.ini文件。
目前的版本更多的考慮是流程功能實現,暫未考慮并發和效率,所以現在跑一個目標花的時間比較久,運行之后會在out/result/目錄生成結果,如圖4.2,圖4.3所示。
圖4.2 運行完成的結果
圖4.3 運行完成之后結果保存在csv文件
環境搭建過程中注意配置config/config.ini文件。目前已知的問題如下所示。
如果生成數據庫的過程中出現很長一段時間無響應,可以使用頁面響應的最后一條指令手動生成數據庫。codeql database create out/database/target_db --language=java --command="/bin/bash -c /Users/xxx/test/CodeQLpy/out/decode/run.sh"由于web.xml配置文件錯誤導致jsp文件反編譯異常的。解決辦法是替換web.xml文件,任意找一個可用的web.xml文件替換即可。
圖4.4異常報錯情況
原創工具下載地址:
??https://github.com/webraybtl/codeQlpy??
如有更好建議歡迎留言交流