想在网上找点 java 代码看看,于是在 gitee 上找到了一个很久不更新的系统,最后一次更新版本还是在两年前。官网由于没有备案也寄了,看来已经不维护了,非常适合我这种新手拿来练习。

gitee 项目源码地址:https://gitee.com/heyewei/JFinalcms

0x01 前言

下载好源码之后,使用 tomcat搭建环境,导入sql 文件,运行

搭建成功,不知道图片干哪去了,其他功能都正常。

随便翻看了一下源码,不是springboot,没有启动程序,存在webapp,但是又存在controller,走路由的,第一时间也迷了,不知道这是什么东西。

于是就看了看这个项目所采用的技术和框架

JFinal?之前根本没听说过。

简介:JFinal 是基于Java语言的极速 WEB + ORM 开发框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful。

JFinal 官方开发文档:https://jfinal.com/doc

0x02 开发基础

config 配置

基于JFinal的web项目需要创建一个继承自JFinalConfig类的子类,在该项目中就是 config/CmsConfig

它有一个启动程序,能跑,但是没有前端的东西,访问啥都404

接着就是定义一些配置了

基础配置,比如说默认路径,编码,数据格式等等。

路由配置,这个后续讲路由解析的时候会说。

数据库配置,获取数据库地址,账号密码建立连接。最后就是添加一些拦截器还有Handler,对请求的数据合法性进行检验。

路由解析

看一个controller

类似于springboot的那种写法,但是这个 RouteMapping 注解是项目自定义的。

但是注解并不能真正解析路由,回头看 config 中的 configRoute 方法

public void configRoute(Routes me) {
// TODO Auto-generated method stub
List<Class<Controller>> controllerClassList = ClassScaner.scanSubClass(Controller.class,true,false);
if (controllerClassList != null) {
for (Class<?> clazz : controllerClassList) {
RouteMapping urlMapping = clazz.getAnnotation(RouteMapping.class);
if (null != urlMapping && StrKit.notBlank(urlMapping.url())) {
me.add(urlMapping.url(), (Class<? extends Controller>) clazz);
}
}
}
}

首先利用 JFinal框架内置的 scanSubClass 方法扫描所有继承于 controller 的子类,然后遍历获取到的子类,获取他们的 RouteMapping 注解信息,也就是路由,然后调用 me.add 将路由与对应的 控制器类对应,实现路由映射。

controller

控制器这边还是需要说一下的,因为它和传统的springboot 项目的控制器不太一样。

它是一个路由对应一个类,并不是一个方法对应一个路由

如图,当我们游览器访问 “/admin/file”,它会默认由 FileController 类的 index 方法,那如何访问到upload 方法呢?在游览器输入 “/admin/file/upload”即可。

知道游览器怎么访问的就够了。

0x03 代码初步分析

看了一眼依赖,没啥特别有用的东西,

这个 commons-collections 当前版本存在反序列化利用链,但是没有反序列化入口

所以从依赖中找个明显的漏洞是不存在的。该框架还自定义的过滤器和handler,

PermissionFilter

先看过滤器 例如这个 PermissionFilter 看意思就是与权限有关的。

两个类似于黑名单的东西

这三个路由是不需要设置权限访问的,接着往下看

开始获取用户认证 session 了,如果用户已经登陆,就能够去访问 permissionExcludes 数组下的路由,在这里做了一个判断,如果该用户为 read 演示账户,则不允许执行更新,删除,备份等操作。这个地方能不能权限绕过呢?我认为是不行的,路由没法用相对路径来绕过。

还有一个 PageCacheFilter 就不看了,处理缓存的,没啥用。

再继续看 handler,handler是请求在被controller接收之间进行处理的。

ResourcesHandler

对 target 也就是访问的url路径进行限制,防止直接访问模板文件,该项目模板文件是通过渲染的。感觉过滤的很死,不那么容易来绕过。

0x04 漏洞分析

1.XSS漏洞分析

反射型xss

该项目是通过 enjoy 将数据渲染到前端模板上的,一般数据插入的格式为 #()

全局搜索,在后台登录的模板文件中找到输入插入的地方

找到登录的逻辑代码

不用看登录的逻辑,这里直接进行了模板的渲染,getPara方法是框架内置的函数,其底层方法为

从 GET 或者 POST 接收传递的参数,这么做是为了将用户输入的账号名留在输入框。总之不存在任何过滤,验证漏洞。

POC:

http://localhost:8080/cms_war/admin/login?username="><script>alert(123);</script>

成功弹窗

存储型xss

黑盒查看功能点,前台是有留言功能的

看一下 guestbook 路由的处理逻辑代码

将提交的数据绑定到 Guestbook 对象上,然后调用 save 方法将数据存储在数据库。

在搜索路由的时候发现 admin 目录下也有 guestbook 路由

游览器访问是查看路由的,前台留言用户提交javascript代码,后台查看留言触发xss攻击。

2.文件读取漏洞

文件操作类,全局搜索 File 函数,在DownController 控制器中。

就是获取 fileKey 参数然后拼接路径,然后将获取到的文件发送给客户端。

这个获取的路径就是war包的绝对路径,在根目录下新建一个txt文件

http://localhost:8080/cms_war/common/down/file?fileKey=../../../../../test.txt

成功下载文件。

3.SSTI

该项目的模板引擎使用的是 enjoy,该模板引擎存在模板注入。

Y4tacker 师傅分析过此漏洞:https://y4tacker.github.io/2022/04/14/year/2022/4/Enjoy%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E5%88%86%E6%9E%90/#%E5%BC%95%E6%93%8E%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B%E7%AE%80%E5%8D%95%E5%88%86%E6%9E%90

我们需要找到能够控制模板的地方,在后台的系统管理的模板管理处

能够直接编辑模板文件,但是测试账号不能修改,没权限

找一个payload

#set((java.beans.Beans::instantiate(null,"javax.script.ScriptEngineManager")).getEngineByExtension("js").eval("function test(){ return java.lang.Runtime};r=test();r.getRuntime().exec(\"calc.exe\")"))

接着访问该模板文件

这里会渲染我们修改的模板文件,访问该路由

成功执行命令。

4.文件上传RCE

全局搜索 render 模板渲染方法,发现有一处能够指定要渲染的模板

点进去看一下

接收html参数,并且做了拼接,可以渲染任意模板,那么接下来找到能够上传文件的地方,比如可以全局搜索 upload 方法,在 FileController 中有处理上传的逻辑。

这里只限制图片格式上传,不过没有影响,剩下的就是重命名,然后将上传的基础信息以json的格式返回给客户端,需要登录后台。

得自己写一个上传表单

上传成功。返回的路径为:

/static/upload/834ca2ae-08a4-43ae-bc41-aa4cc55c2964.jpg

然后执行渲染我们上传的文件。

http://localhost:8080/cms_war/ajax/html?html=../../static/upload/fc96f538-29b4-435d-a0a8-b4a55861d0db.jpg

不能重启 tomcat ,不然上传的文件就没了。

成功弹出计算器。

5.文件写入

在后台模板管理中有新增模板的功能。

文件名和文件内容都用户可控,存在安全隐患,用户可以上传jsp文件

这里做了一个简单的拼接,然后调用 write 方法去写入文件。

好像只能在当前目录有写权限,写一个蚁剑的JSP一句话木马

写完之后用蚁剑连接,

可以成功执行命令。但是在web端执行命令为什么不可以呢

注意 JFinal 不支持jsp,所以 JSP 后缀要url编码一下。

6.SQL注入

全局搜索一些例如 like = % 等可能会存在拼接的sql字符。

发现一处拼接,跟进代码

就普通的拼接,然后执行。回溯控制器方法

完全无过滤,直接开始注入。输入测试POC

http://localhost:8080/cms_war/admin/admin?name=%25'+and+sleep(4)+or+'%25

打进去后真的会延时4秒,只能盲注,报错注入也不行。

用sqlmap 盲注得到 数据库名

python sqlmap.py -u "http://localhost:8080/cms_war/admin/admin?name=1" --cookie="JSESSIONID=0E915EDA1AF636D5EBDD770AFB5773B6;" --dbs

0x05 结语

漏洞复现参考于:https://xz.aliyun.com/t/13259

得开始找一些知名的框架审计了。