首页 > 其他分享 >OFcms代码审计

OFcms代码审计

时间:2024-10-12 11:26:54浏览次数:1  
标签:审计 文件 file String 代码 File OFcms new dir

前言

今天看到一篇审计Java的文章,于是在没有继续系统学习基础的情况下,又一次当其搬运工,打算熟悉熟悉java项目搭建流程,然后再跟着过一下Java代码审计流程,全文除搭建的坑,其余漏洞代码分析均CV大佬思路。。。

环境搭建

  • 源码地址:https://gitee.com/oufu/ofcms
  • 环境依赖:
    a. 建议采用 idea 工具开发
    b. mysql 5.6+
    c. jdk 1.8
    d. tomcat 8 5.通过war包直接放TOMCAT下面 到附件中下载
    e. phpstudy

大致安装步骤:

  • 首先就是直接用idea打开下载好的源码文件
  • 然后将ofcms-master\ofcms-master\ofcms-admin\src\main\resources\dev\conf目录下的db-config.properties 改成db.properties 修改数据库连接
  • 修改db.properties中的数据库密码为本地设置的mysql密码
  • 配置好数据库后,先用IDEA中的数据连接插件Database Navigator测试数据库连通性(插件安装步骤见:https://blog.csdn.net/Liu_wen_wen/article/details/125958039)
  • 安装成功后,在视图->工具窗口中选中DB Browser,配置本地数据库用户名密码


  • 连接成功后返回数据表项
  • 安装Maven依赖,这个其实在你打开项目的时候,idea就自动检测了,一般等他下载完就可以了
  • 这里要是下载太慢可以配置阿里加速源
  • 若是使用 IDEA 自带的 maven,从 IDEA 所在目录开始:IntelliJ IDEA\plugins\maven\lib\maven3\conf\settings.xml。若是自己下载的 maven 则从 IDEA 所在目录开始:maven的安装目录\conf\settings.xml。
  • 将其中的 url 进行修改。(可用 ctrl + F 进行直接搜索)
<mirror>
  <id>mirrorId</id>
  <mirrorOf>repositoryId</mirrorOf>
  <name>Human Readable Name for this Mirror.</name>
  <url>https://maven.aliyun.com/repository/public</url>
</mirror>

  • 安装tomcat,社区版不自带这个,我之前是装了smart tomcat,这里直接在设置->插件中下载就行,下载好,重启idea就行,就会在设置界面中多一个tomcat server

  • 配置tomcat

  • 选择好之后,进行部署到ofcms-admin

  • 全部完成后,就可以构建项目了,项目构建成功后,访问http://localhost:8080/ofcms-admin/即可以安装

  • 这里还有几个坑,要是在本地的phpstudy中使用mysql,直接导入ofcms-V1.1.3\doc\sql\ofcms-v1.1.3.sql文件,再重启即可

  • 然后刷新页面即安装成功

  • PS:构建过程中若出现junit缺少jar包,在文件->项目结构->库中点击加号,然后弹出对话框,选择Java,找到idea的安装目录lib下,选择junit的jar包,选择junit.jar,然后重新构建即可

漏洞审计复现

任意文件读取

漏洞分析

这个文件的getTemplates函数,可以看到从前台获取dir、up_dir、res_path值,直接把dir拼接到pathfile,并未对其处理,直接获取pathfile目录下的所有目录dirs和文件files,但是获取的文件后缀只能是html、xml、css、js
这里先看一下java的文件拼接
示例代码如下:

import java.io.File;

public class Main {
    public static void main(String[] args) {
        File file3 = new File("D:\\workspace_idea1","JavaSenior");
        System.out.println(file3);
    }
}

找了一个在线平台(https://www.bejson.com/runcode/java/)运行一下后,发现File类,会直接拼接路径
public File(String parent,String child) 以parent为父路径,child为子路径创建File对象。

![](/i/l/?n=24&i=blog/1973814/202410/1973814-20241012111334757-1973437600.png)

源码:
public void getTemplates() {
    //当前目录
    String dirName = getPara("dir","");
    //上级目录
    String upDirName = getPara("up_dir","/");
    //类型区分
    String resPath = getPara("res_path");
    //文件目录
    String dir = null;
    if(!"/".equals(upDirName)){
        dir = upDirName+dirName;
    }else{
        dir = dirName;
    }
    File pathFile = null;
    if("res".equals(resPath)){
        pathFile = new File(SystemUtile.getSiteTemplateResourcePath(),dir);
    }else {
        pathFile = new File(SystemUtile.getSiteTemplatePath(),dir);
    }

    File[] dirs = pathFile.listFiles(new FileFilter() {
        @Override
        public boolean accept(File file) {
            return file.isDirectory();
        }
    });
    if(StringUtils.isBlank (dirName)){
        upDirName = upDirName.substring(upDirName.indexOf("/"),upDirName.lastIndexOf("/"));
    }
    setAttr("up_dir_name",upDirName);
    setAttr("up_dir","".equals(dir)?"/":dir);
    setAttr("dir_name",dirName.equals("")?SystemUtile.getSiteTemplatePathName():dirName);
    setAttr("dirs", dirs);
    /*if (dirName != null) {
pathFile = new File(pathFile, dirName);
}*/
    File[] files = pathFile.listFiles(new FileFilter() {
        @Override
        public boolean accept(File file) {
            return !file.isDirectory() && (file.getName().endsWith(".html") || file.getName().endsWith(".xml")
                                           || file.getName().endsWith(".css") || file.getName().endsWith(".js"));
        }
    });
    setAttr("files", files);
    String fileName = getPara("file_name", "index.html");
    File editFile = null;
    if (fileName != null && files != null && files.length > 0) {
        for (File f : files) {
            if (fileName.equals(f.getName())) {
                editFile = f;
                break;
            }
        }
        if (editFile == null) {
            editFile = files[0];
            fileName = editFile.getName();
        }
    }

    setAttr("file_name", fileName);
    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);
        }
    }
    if("res".equals(resPath)) {
        render("/admin/cms/template/resource.html");
    }else{
        render("/admin/cms/template/index.html");
    }
}

进而在从前台获取file_name参数,默认为index.html,再判断files是否为空,如果不为空,循环所有文件files和file_name进行对比,有则返回该文件,无则返回所有文件files的第一个文件,最终读取该文件内容

复现

任意文件写入

漏洞分析

文件位置:
ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/cms/TemplateController.java
在TemplateController.java文件里的save函数,从前台主要获取file_name、file_content两个参数,可以发现该函数的file_name是直接和pathfile目录拼接上的,所以是可以路径穿越的,导致文件可以写到任意位置下

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();
}
// getParameter获取POST/GET传递的参数值

复现

在admin目录下写一个a.xml,默认会写在根目录下:

这里在写入完文件后,会发现全局也搜不到文件位置,但是又显示处理成功,那么为了能验证效果,这里要配合一下上面的任意文件读取,这也就是为什么要创建一个.xml文件的原因

模板注入(危害程度不大,仅能影响自己系统)

漏洞分析

在后台的模板管理处,可以直接修改模板语句导致模板注入

直接在index.html中插入如下语句:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("calc") }

然后访问首页

SQL注入

漏洞分析

文件位置
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,没有经过任何过滤
跟了一下函数实现,发现也是直接使用的executeUpdate()

int update(Config config, Connection conn, String sql, Object... paras) throws SQLException {
    PreparedStatement pst = conn.prepareStatement(sql);
    config.dialect.fillStatement(pst, paras);
    int result = pst.executeUpdate();
    DbKit.close(pst);
    return result;
}

复现

payload:sql=update of_cms_ad set ad_id=updatexml(1,concat(1,user()),1)

任意文件上传

漏洞分析

文件位置:ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/ComnController.java
这个文件下的ComnController.java、UeditorAction.java文件,其中的upload、editUploadImage、uploadImage、uploadFile、uploadVideo和uploadScrawl函数都是可以进行上传的,可以看到用的都是getFile函数:

  • 漏洞代码(ComnController.java)
public void upload() {
    try {
        UploadFile file = this.getFile("file", "image");
        file.getFile().createNewFile();
        Map<String, Object> data = new HashMap<String, Object>();
        data.put("filePath", "/upload/image/" + file.getFileName());
        data.put("fileName", file.getFileName());
        rendSuccessJson(data);
    } catch (Exception e) {
        rendFailedJson(ErrorCode.get("9999"));
    }

先看一下upload函数,其中getFile函数先获取文件和上传路径,然后创建新文件,跟一下getFile函数

public UploadFile getFile(String parameterName, String uploadPath) {
    this.getFiles(uploadPath);
    return this.getFile(parameterName);
}

这里又用getFiles处理上传路径,然后返回文件名,继续跟一下getFiles函数

public List<UploadFile> getFiles(String uploadPath) {
    if (!(this.request instanceof MultipartRequest)) {
    this.request = new MultipartRequest(this.request, uploadPath);
}

这里if判断请求类型不是MultipartRequest后会用MultipartRequest继续处理请求和上传路径,那么再继续跟一下MultipartRequest

public MultipartRequest(HttpServletRequest request, String uploadPath) {
    super(request);
    this.wrapMultipartRequest(request, this.getFinalPath(uploadPath), maxPostSize, encoding);
}

这里先调用了父类的构造方法,然后又调用wrapMultipartRequest处理请求和文件上传路径,继续跟一下wrapMultipartRequest

private void wrapMultipartRequest(HttpServletRequest request, String uploadPath, int maxPostSize, String encoding) {
    File dir = new File(uploadPath);
    if (!dir.exists() && !dir.mkdirs()) {
        throw new RuntimeException("Directory " + uploadPath + " not exists and can not create directory.");
    } else {
        this.uploadFiles = new ArrayList();

        try {
            this.multipartRequest = new com.oreilly.servlet.MultipartRequest(request, uploadPath, maxPostSize, encoding, fileRenamePolicy);
            Enumeration files = this.multipartRequest.getFileNames();

            while(files.hasMoreElements()) {
                String name = (String)files.nextElement();
                String filesystemName = this.multipartRequest.getFilesystemName(name);
                if (filesystemName != null) {
                    String originalFileName = this.multipartRequest.getOriginalFileName(name);
                    String contentType = this.multipartRequest.getContentType(name);
                    UploadFile uploadFile = new UploadFile(name, uploadPath, filesystemName, originalFileName, contentType);
                    if (this.isSafeFile(uploadFile)) {
                        this.uploadFiles.add(uploadFile);
                    }
                }
            }

        } catch (ExceededSizeException var12) {
            throw new com.jfinal.upload.ExceededSizeException(var12);
        } catch (IOException var13) {
            throw new RuntimeException(var13);
        }
    }
}

这里可以看到详细的文件上传时文件处理了,先是根据给出的上传路径创建目录路径,会事先判断目录是否存在或者是否可以创建,全都满足后,即可进入下一步判断文件名是否为空,最后在进入isSafeFile函数进行文件类型判断,所以这里再跟一下isSafeFile函数

  private boolean isSafeFile(UploadFile uploadFile) {
       String fileName = uploadFile.getFileName().trim().toLowerCase();
       if (!fileName.endsWith(".jsp") && !fileName.endsWith(".jspx")) {
           return true;
       } else {
           uploadFile.getFile().delete();
           return false;
       }
   }

这里先将文件名转为小写,然后判断是否为jsp和jspx后缀,若是则删除文件,所以这里应该是要绕过的,但是这里我们可以利用Windows或中间件文件上传特性来避免结尾为jsp或jspx
复现
漏洞路径http://localhost:8080/ofcms-admin/admin/comn/service/upload
随意找一下一个上传路径

然后抓包

改为image/jpg,并且文件名后多加一个点,即为shell.jsp.,即可以上传成功

到文件路径下查看

本来想用冰蝎连一下,发现连不了,最后在参考文章的最后看到了这样一句话,关于上传jsp文件为什么执行不了——是因为存在jfinal过滤器,所以没有办法传上去。

XXE漏洞

漏洞分析

文件位置:ofcms-admin\src\main\java\com\ofsoft\cms\admin\controller\ReprotAction.java
其中的expReport方法

	public void expReport() {
        
		HttpServletResponse response = getResponse();// 获取响应包
		Map<String, Object> hm = getParamsMap(); // 将getParamsMap返回的result Map类型赋值给Map类型的数据集hm
		String jrxmlFileName = (String) hm.get("j");// 传参j(string类型)赋值给jrxmlFileName
		jrxmlFileName = "/WEB-INF/jrxml/" + jrxmlFileName + ".jrxml";//拼接路径+文件名+后缀给jrxmlFileName
		File file = new File(PathKit.getWebRootPath() + jrxmlFileName);//创建file实例,路径,参数给出
		String fileName = (String) hm.get("reportName");//从请求中获取reportName赋值给fileName
		log.info("报表文件名[{}]", file.getPath());//返回信息报表文件名路径
		 OutputStream out = null;
		try {
			DataSource dataSource = (DataSource) SysBeans
					.getBean("dataSourceProxy");
			JasperPrint jprint = (JasperPrint) JasperFillManager.fillReport(
					JasperCompileManager
							.compileReport(new FileInputStream(file)), hm,
					dataSource.getConnection());
			JRXlsExporter exporter = new JRXlsExporter();
			response.setHeader("Content-Disposition", "attachment;filename="
					+ URLEncoder.encode(fileName, "utf-8") + ".xls");
			response.setContentType("application/xls");
			response.setCharacterEncoding("UTF-8");
			JasperReportsUtils.render(exporter, jprint,
					response.getOutputStream());
			response.setStatus(HttpServletResponse.SC_OK);
			 out=response.getOutputStream();
			 out.flush();
	         out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		renderNull();
//		renderJson();
	}

getParamsMap() 有传参
后边将输入的值传递给 hm 和 jrxmlFileName
服务器接收用户输入的j参数后,拼接生成文件路径,这里没有进行过滤,可以穿越到其它目录,但是限制了文件后缀为jrxml。
定位 getParamsMap()方法 没有过滤

    public Map<String, Object> getParamsMap() {
        Map<String, String[]> params = getParaMap();//定义一个返回map类型数据集的params参数,将获取到的请求参数的键值赋给params
        Map<String, Object> result = new ConcurrentHashMap<String, Object>();//定义一个map类型数据集的result参数, 实例化方法,允许一边更新一遍遍历
        for (String value : params.keySet()) {
            result.put(value, params.get(value)[0]);// for 循环,获取params的全部键和值写入result map类型数据集中
        }
        return result; //返回result map类型的数据集
    }

接下来会调用JasperCompileManager.compileReport()方法,跟进该方法看看

try {
			DataSource dataSource = (DataSource) SysBeans
					.getBean("dataSourceProxy");
			JasperPrint jprint = (JasperPrint) JasperFillManager.fillReport(
					JasperCompileManager
							.compileReport(new FileInputStream(file)), hm,
					dataSource.getConnection());
			JRXlsExporter exporter = new JRXlsExporter();
			response.setHeader("Content-Disposition", "attachment;filename="
					+ URLEncoder.encode(fileName, "utf-8") + ".xls");
			response.setContentType("application/xls");
			response.setCharacterEncoding("UTF-8");
			JasperReportsUtils.render(exporter, jprint,
					response.getOutputStream());
			response.setStatus(HttpServletResponse.SC_OK);
			 out=response.getOutputStream();
			 out.flush();
	         out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

在compileReport方法中又调用了JRXmlLoader.load()方法,继续跟踪

复现

复现锤子啊,这个xxe貌似是旧版本的,新版本的compileReport函数已经被修复了。

小结

整体上说算是完整的搭建和跟了一遍整体代码,发现对很多java的基本函数了解程度还是不够,需要继续进一步学习,其中有部分漏洞算是自己重新分析了一下,其他的就是跟这个大佬的文章复现的。这个cms应该还是有很多洞的,等把炼石计划的java基础学完,再继续重新分析一波吧,尽请期待OFcms代码审计(二)!!!无限期拖更!!!

标签:审计,文件,file,String,代码,File,OFcms,new,dir
From: https://www.cnblogs.com/Konmu/p/18460151

相关文章

  • PTA 作业六 JAVA 面向对象程序设计6-2 sdut-oop-list-1 学生集合(类、集合)作者 周雪芹
    6-2sdut-oop-list-1学生集合(类、集合)分数15作者 周雪芹单位 山东理工大学以下程序不完整,请你根据已经给出的程序代码中表达的题意,以及程序的输入、输出信息,完成Student类的设计,补全代码。函数接口定义:classStudent{}裁判测试程序样例:importjava.util.ArrayLi......
  • 如何将本地代码打包到测试环境?(前端和后端)
    前几天晚上,睿哥教了我如何将本地的代码打包并部署到测试环境上。然而,他讲得有点快,我可能还不是很熟悉。趁着现在有空,我决定把他讲的内容记录下来,以免以后忘记。由于我现在同时负责前端、后端和小程序的开发,这三种技术我都需要掌握。本文将首先讲解如何将后端代码打包并部署到......
  • RBAC管理系统审计记录
    RBAC管理系统审计记录环境搭建环境依赖Windowsidea2022jdk8RBAC源码phpstudy的mysql5.6.7简易搭建流程(Windows下)直接使用idea打开项目,然后选中右上角的项目构建项目中有几处需要修改:○1、要开启phpstudy的mysql,然后创建rbac数据库,并将源码中的rbac.sql数据导入......
  • 增广拉格朗日iLQR时空联合规划代码简介与再开发3-iLQR目录
    增广iLQR-时空联合规划算法代码简介与再开发-前言_时空联合优化器-CSDN博客文章浏览阅读294次,点赞6次,收藏11次。简单来说就是同时求解路径与速度曲线。时空联合规划本质上是求解最优化问题,将路径和速度曲线作为优化问题的变量,同时得到二者在可行范围内的最优解。前言介绍LQR和......
  • [双体系练习]静态代码块中不能直接调用类中实例方法
    静态代码块中不能? · 【D】A.初始化静态变量 B.调用静态方法C.new对象 D.直接调用类中实例方法(实例方法是非静态方法,非静态的无法直接调用)注意:在静态代码块中创建的对象实例会在类加载时就创建,并且这些实例在整个应用程序生命周期中都会占用内存。......
  • NocoBase 与 Appsmith:哪个低代码平台更适合你?
    欢迎回到我们深度对比系列文章,这是本系列的第三篇。在之前我们已经与两个非常优秀的产品进行了对比:NocoBase与NocoDB:开源无代码工具深度对比CRUD开发工具NocoBase与Refine对比今天,让我们把目光移向Appsmith。NocoBase和Appsmith均为开源的低代码/无代码开发平台,这......
  • Java在图片上写字生成新图片的代码实现
    引言在图像处理领域,有时我们需要在图片上添加文字,以生成带有特定信息的新图片。Java作为一种功能强大的编程语言,提供了多种库和工具来实现这一需求。本文将详细介绍如何使用Java在图片上写字,并生成一张新的图片。准备工作在开始编写代码之前,我们需要确保已经安装了Java开......
  • 【反转链表】【K个一组翻转链表】两个问题具体思路,文中含多种解法(附完整代码)
    文章目录前言一、如何理解反转链表?二、反转链表1.方法一(递归)方法二(迭代)三、K个一组翻转链表前言本文将围绕【反转链表】问题展开详细论述。采用【递归法】【迭代法】同时,还将进一步升级该问题,讨论【K个一组翻转链表】一、如何理解反转链表?题目链接:[点击......
  • 代码随想录训练营第60天|冗余连接
    108.冗余连接#include<iostream>#include<vector>usingnamespacestd;intn;//节点数量vector<int>father(1001,0);//按照节点大小范围定义数组//并查集初始化voidinit(){for(inti=0;i<=n;++i){father[i]=i;}}//并查集......
  • 图像去雾综述-图像去雨综述(代码+教程)
    图像去雾是一种针对雾霾、雨雾等大气干扰因素引起的图像模糊和降低对比度的现象进行处理的技术。在现实生活中,这种现象常常会影响到图像的质量,使得图像难以清晰地表现出真实景物。为了解决这个问题,研究人员开发了各种图像去雾算法。本文将对当前主流的图像去雾算法进行综述......