Openrasp是百度關於rasp技術的開源項目,由於工作需要,之前對rasp的源碼進行了簡單的分析。文章是之前就寫好的,現在放出了,希望對大家有寫幫助。
OpenRASP中java引擎的源碼分析
安裝包解壓后目錄結構如下:

RaspInstall.jar是OpenRASP中java引擎的安裝(卸載)運行文件,在終端中執行以下命令進行安裝(以tomcat為例):
java -jar RaspInstall.jar -install <tomcat_root>
值得注意的是:<tomcat_root>不是webapps目錄,而是tomcat的根目錄
rasp目錄夾中java引擎的資源文件,目錄結構如下圖:

conf文件夾下是rasp.properties配置文件,里面的配置參數用於指定是否開啟基線檢測、攔截后跳轉地址等等。
plugins文件夾下是official.js文件,OpenRASP是通過JavaScript插件來實現檢測邏輯的,而official.js就是這個檢測插件。
rasp.jar和rasp-engine.jar是java引擎的核心文件
一:RaspInstall.jar
反編譯RaspInstall.jar文件后目錄如下:

簡單看下App.class的代碼

以安裝操作為例,獲取命令行第二個參數(安裝時指定web容器的根目錄)后,先調用了newInstallerFactory()方法對操作系統類型進行判斷,然后返回不同系統的安裝工廠類(都繼承了InstallerFactory類)

隨后調用了工廠類父類InstallerFactory的getInstaller(File ServerRoot)方法

父類中getInstaller(File ServerRoot)就是對web容器做了一個判斷,然后再調用子工廠類的getInstaller(String aerverName,String serverRoot)方法,方法中調用了不同的web容器安裝類

后面就是具體的安裝操作了,包括在web服務器下創建rasp目錄,將資源文件放入進去;添加環境變量;配置容器啟動參數(例如java引擎是通過java agent機制實現的,應用啟動時需要指定-javaagent參數)。
二:rasp.jar
rasp.jar文件就是一個javaagent,會在web應用的main函數運行之前先運行。目錄結構如下:

之前也說了rasp.jar文件就是一個javaagent,而Agent.java就是這個javaagent的入口類,內容如下:

Premain(String agentArg, Instrumentation inst)就是javaagent的入口函數。
函數中先調用了JarFileHelper.addJarToBootstrap(inst)

函數中將當前jar文件加入到了jdk的根路徑下,優先加載。這是因為當去 hook 像 java.io.File 這樣由 BootstrapClassLoader 加載的類的時候,無法從該類調用非 BootstrapClassLoader 加載的類中的接口,所以 agent.jar 會先將自己添加到 BootstrapClassLoader 的ClassPath下,這樣 hook 由 BootstrapClassLoader 加載的類的時候就能夠成功調用到 agent.jar 中的檢測入口。
隨后調用了ModuleLoader.load(agentArg, inst),用於加載所有的rasp模塊,目前只有一個模塊就是rasp-engine.jar.(目前還不是很能理解多個模塊的用意)

方法中new了一個ModuleLoader(agentArg, inst),用於構造所有的模塊。

大概意思就是調用各rasp模塊入口類中的start方法。
三:rasp-engine.jar
先從rasp-engine.jar的入口文件EngineBoot.java中的入口方法start看起。

先是判斷應用的conf目錄下是否有日志配置文件rasp-log4j.xml,如果不存在則釋放log4j日志配置文件,也就是將資源文件夾resources下的rasp-log4j.xml.default文件中的注釋去掉,文件名修改后復制到conf文件夾下。
readVersion()用於通過/META/MANIFEST.MF文件讀取當前openrasp的版本。
接下來調用了JsPluginManager.init(),用於初始化插件系統以及更新插件,跟進去看看:

JSContextFactory.init()主要用於JS上下文類的初始化,由於涉及到Rhino框架(一個開源的腳本引擎框架)的一些東西,沒有深入了解了。目前知道的是有加載了資源文件夾resources下environment文件夾下的js文件,以及初始化一些屬性。updatePlugin()方法中更新插件引擎。initFileWatcher()初始化一個文件時間的監視器,監測plugin目錄下的js文件是否有更新。
回到start方法中,初始化JS引擎后,接下來調用了CheckerManager.init(),該方法用於初始化Checker管理器,也就是將所有的checker類實例都放入EnumMap中,方便之后通過Type來判斷調用各自的Check類中的check()方法。最后就是調用initTransformer(inst)之后整個啟動流程就結束了,這個方法用於初始化類字節碼轉換器,里面主要做了兩件事(分為兩部分):1.給類加載操作進行了插樁操作,當類加載的時候會先進入agent進行處理 2.對於初始化前就已經加載的類中執行了retransform處理。跟入函數:

第一部分:函數內先構造了一個自定義類字節碼轉換器,該轉換器的構造函數中會自動掃描所有標有@HookAnnotation的類,並放入HashMap中,方便后面比較類加載器加載的類是否是我們想要hook的類。
接下來通過addTransformer將這個定義類字節碼轉換器添加到Instrumentation中,(這里涉及到javaagent的知識點,當Instrument讀取字節碼文件后會自動調用回調函數ClassFileLoadHook,該回調函數中做了很多事,其中一件就是調用ClassFileTransformer接口實現類對象中的transform方法)在這里也就是調用了CustomClassTransformer類中的transform方法,跟進該方法

該方法中主要是對目前類加載器所加載的類是否屬於眾多我們想要hook類的一種,如果是則調用對應hook類中的transformClass方法。在for循環中,取出了所有的hook類並通過hook類中各自的isClassMatched方法域當前類加載器所加載的類相比較,如果是我們想要hook的類,則通過AbstractClassHook(所有hook類的父類)中的transformClass方法來調用對應hook類中的hookMethod方法,該方法就是用於將待轉換的類轉換后以字節碼數組的形式返回,這里的轉換就是通過操作字節碼在想要hook的類的關鍵函數前后加入安全檢測代碼。
這里以XXEHook這個hook類為例,跟進它hookMethod方法:

先是調用了getInvokeStaticSrc方法,該方法通過輸入的參數獲取靜態方法的代碼字符串並返回(在原方法基礎上加了一些捕獲異常的代碼),接下來的insertBefore方法是對javassit框架中CtBehavior類的insertBefore方法做了包裝,主要內容是通過字節碼的形式在指定方法前加入指定的安全檢測代碼(也就是插樁技術,具體如果操作字節碼是交給了javassit框架在做,我們只需要調用它的接口並指定想要插樁的函數插入的代碼就行),該方法的第一個參數當前類加載器所加載的類的字節碼(這個地方有點說不清楚!),第二個參數指定該類中想要進行插樁操作的方法的方法名,第三個參數是對該類中想要進行插樁操作的方法的參數以及返回類型的描述((Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V代表該方法有四個參數且都是String類型,返回void),第四個參數就是我們想要插入的代碼字符串了。
具體的插入的什么安全檢測代碼,我們之后再回過來看,先不繼續跟進了。這里附上一張官網關於Hook Class流程的說明:

回到initTransformer(inst)函數中來看第二部分:

顯示調用了instrumentation.getAllLoadedClasses()用於獲取所有加載過的類,for循環中對這些類做了一個判斷,是否為需要hook的類,如果是則調用inst.retransformClasses(Class cls)像jvm發起重新轉換的請求。
到這里整個openrasp啟動流程已經結束了,流程圖如下:

現在再回過頭看看具體插入的安全檢測代碼是什么樣子的,還是以XXEHook這個hook類為例。XXEHook類中插入的代碼是checkXXE這個方法:

先對expandedSystemId參數做了校驗,是否為空或者是否為當前線程已觸發過檢測的expandedSystemId。
如果是第一次觸發檢測通過XXEHook.getLocalExpandedSystemIds().add(expandedSystemId)將當前expandedSystemId加入到HashSet中,代表已經檢測過。。。。(未完待續)
整個rasp-engine.jar的目錄結構如下

hook文件夾目錄結構如下

Plugin文件夾目錄如下

Tool文件夾目錄如下

