突破WAF!帆软模板注入漏洞利用新姿势
前言
24年9月份的时候打攻防遇到一个帆软报表版本为v11,从/webroot/decision/system/info可以看到细的版本号为模版注入修复前的版本。于是直接使用/webroot/decision/view/ReportServer?test=exp进行利用发现被WAF拦截,经过测试这个地方很难用常规的方法绕过WAF。
分析源码
因为比较难从传统的办法绕过WAF,于是转而分析源码看是否存在一些其他的绕过方式。
其实之前就对这个漏洞进行过分析,从公开的POC路由可以直接搜索/view/ReportServer

然后再搜索com.fr.web.controller.ViewRequestConstants#REPORT_VIEW_PATH_COMPATIBLE哪里被调用

找到对应实现类和方法,正常来说的话这里可以直接在idea里两下shift直接查找路由但是不知道为啥idea没识别到,猜测可能是帆软改了Controller注解的包名导致的。com.fr.web.controller.ReportRequestCompatibleService#preview

这里直接使用的getQueryString获取我们输入的查询参数且不会URL解码所以有很多特殊字符也不能输入,所以想从这个地方找到一些绕过WAF的办法比较难,除非去看还有哪些比较的特殊的模板方法能用来编码解码。但是经过测试发现WAF对于${..}特别敏感就算找到一些特殊的模板方法估计也没用。因为帆软报表以前也出过这个类型的模版注入,想着应该还有其他地方可以前台触发这个漏洞。于是通过jadx直接搜索com.fr.base.TemplateUtils#render(java.lang.String)、com.fr.base.TemplateUtils#renderParameter4Tpl这类sink发现搜索结果有点多找起来的话比较麻烦。在阅读源码的过程中发现大多数进入这两个sink的字符串都符合如下正则特征"\$\{.*\}.*\+于是使用jadx直接搜索。

排除参数不可控、以及也是使用getQueryString的很快定位到com.fr.nx.app.web.v9.handler.handler.PDFPrintPrintForIEHandler#handleRequest
protected void handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception {
String var3 = SessionPoolManager.getOrGenerateSessionIDWithCheckRegister(var1, var2);
if (var3 != null) {
VersionTransition.saveCalculatorContext(var1, "/view/report");
String var4 = "${servletURL}?op=export&sessionID=" + var3 + "&format=pdf&frandom=" + Math.random() + System.currentTimeMillis() + "&isPDFPrint=true&extype=ori";
String var6 = WebUtils.getHTTPRequestParameter(var1, "codebase");
String var5;
if ("true".equals(var6)) {
var5 = "<OBJECT ID='PDFReader' WIDTH='100%' HEIGHT='100%' CLASSID='CLSID:CA8A9780-280D-11CF-A24D-444553540000'";
var5 = var5 + " codebase=\"${servletURL}?op=resource&resource=/AdobeReader.exe\">";
var5 = var5 + "<param name='src' value='" + var4 + "'></OBJECT>";
} else {
var5 = "<OBJECT ID='PDFReader' WIDTH='0' HEIGHT='0' CLASSID='CLSID:CA8A9780-280D-11CF-A24D-444553540000'><param name='src' value='" + var4 + "'></OBJECT>";
}
PrintWriter var7 = WebUtils.createPrintWriter(var2);
var7.print(TemplateUtils.render(var5));
var7.flush();
var7.close();
}
}
先从请求中获取sessionID然后拼接进var4再拼接进var5最后进入render触发模版注入。然后我们查找在哪里使用了PDFPrintPrintForIEHandler

找到入口方法com.fr.nx.app.web.controller.NXController#pdfPrintForIEV9其路由为/webroot/decision/nx/report/v9/print/ie/pdf

设置了请求方式仅为GET其实我本来是想找POST的这类路由的因为POST肯定比较好绕一点。但是后面查看获取sessionID的方式时发现这里存在多种编码方式可以绕过WAF。我们跟入com.fr.web.core.SessionPoolManager#getOrGenerateSessionIDWithCheckRegister查看如何获取sessionID
public static String getOrGenerateSessionIDWithCheckRegister(HttpServletRequest var0, HttpServletResponse var1) throws Exception {
String var2 = NetworkHelper.getHTTPRequestSessionIDParameter(var0);
if (var2 == null) {
var2 = generateSessionIDWithCheckRegister(var0, var1);
}
return var2;
}
一直跟下去最后会到getHTTPRequestEncodeParameter
public static String getHTTPRequestEncodeParameter(HttpServletRequest var0, String var1, boolean var2) {
ExtraClassManagerProvider var3 = (ExtraClassManagerProvider)PluginModule.getAgent(PluginModule.ExtraCore);
Object var4;
if (var3 == null) {
var4 = DefaultRequestParameterHandler.getInstance();
} else {
var4 = (RequestParameterHandler)var3.getSingle("RequestParameterHandler");
if (var4 == null) {
var4 = DefaultRequestParameterHandler.getInstance();
}
}
Object var5 = ((RequestParameterHandler)var4).getParameterFromHeader(var0, var1);
if (var5 == null) {
var5 = ((RequestParameterHandler)var4).getParameterFromRequest(var0, var1);
}
if (var5 == null) {
var5 = ((RequestParameterHandler)var4).getParameterFromAttribute(var0, var1);
}
if (var5 == null) {
var5 = ((RequestParameterHandler)var4).getParameterFromJSONParameters(var0, var1);
}
if (var5 == null) {
var5 = ((RequestParameterHandler)var4).getParameterFromSession(var0, var1);
}
if (var5 == null) {
var1 = CodeUtils.cjkEncode(var1);
var5 = ((RequestParameterHandler)var4).getParameterFromRequest(var0, var1);
if (var5 == null) {
var5 = ((RequestParameterHandler)var4).getParameterFromAttribute(var0, var1);
if (var5 == null) {
var5 = ((RequestParameterHandler)var4).getParameterFromSession(var0, var1);
}
}
}
return var2 ? checkURLDecode(var5) : GeneralUtils.objectToString(var5);
}
这里var1=sessionID,var2=true这个方法里通过多种方式获取参数值。
- Request.getHeader里获取
- Request.getParameter获取
- Session.getAttribute获取
- getParameterFromJSONParameters
- Request.getAttribute获取
所以我们这里可以用来获取的途径有三种getHeader|getParameter|getParameterFromJSONParameters注意到最后return的时候var2=true就会进入checkURLDecode
private static String checkURLDecode(Object var0) {
if (var0 == null) {
return null;
} else {
String var1 = CommonCodeUtils.decodeText(String.valueOf(var0));
try {
return URLDecoder.decode(var1, "UTF-8");
} catch (UnsupportedEncodingException var3) {
return null;
} catch (IllegalArgumentException var4) {
return var1;
}
}
}
这里会先调用decodeText进行解码再调用URLDecoder解码。跟入decodeText最后会调用com.fr.stable.CommonCodeUtils#cjkDecode进行解码
public static @NotNull String cjkDecode(@Nullable String text) {
if (text == null) {
return "";
} else if (!isCJKEncoded(text)) {
return text;
} else {
StringBuilder newTextBuf = new StringBuilder();
for(int i = 0; i < text.length(); ++i) {
char ch = text.charAt(i);
if (ch == '[') {
int rightIdx = text.indexOf(93, i + 1);
if (rightIdx > i + 1) {
String subText = text.substring(i + 1, rightIdx);
if (subText.length() > 0) {
ch = (char)Integer.parseInt(subText, 16);
}
i = rightIdx;
}
}
newTextBuf.append(ch);
}
return newTextBuf.toString();
}
}
对应的编码方法为com.fr.stable.CommonCodeUtils#cjkEncode
public static @NotNull String cjkEncode(@Nullable String text) {
if (text == null) {
return "";
} else {
StringBuilder newTextBuf = new StringBuilder();
int i = 0;
for(int len = text.length(); i < len; ++i) {
char ch = text.charAt(i);
if (needToEncode(ch)) {
newTextBuf.append('[');
newTextBuf.append(Integer.toString(ch, 16));
newTextBuf.append(']');
} else {
newTextBuf.append(ch);
}
}
return newTextBuf.toString();
}
}
所以我们可以将我们的payload先URL编码再使用cjkEncode编码进行利用从而绕过WAF进行模版注入。按照上述思路进行测试后发现生成的payload长度太长了,我们这个新找的接口是GET型的所以payload长度太长的话会直接导致tomcat报错。于是换了一个简短一些的写文件马,以及在cjkEncode编码的时候只编码非字母非数字字符,然后将payload放入header中,成功绕过WAF写入文件。


但是发现webshell没有正常解析,应该是windows然后使用Anchor师傅的解决办法,使用/webroot/decision/file接口初始化JasperInitializer

再次访问webshell成功解析

上面的编码方式实际上在帆软的大多数获取参数值的场景都可以使用。
总结
在WAF越来越强且使用越来越广的高对抗情况下,我们除了使用传统的绕过方法还可以从漏洞代码出发寻找其他漏洞利用路径以及可能存在的某种编码方式或解析差异绕过WAF进行攻击。对于0day挖掘人员在进行漏洞挖掘利用的过程中也应深入源码查看是否有某种特定的方式可以使得我们的payload不具备明显特征,这样在利用的过程中也能减少被发现的可能,提高0day的存活时间。
如需编码脚本进行研究可关注公众号漫漫安全路,回复fr得到下载地址。
本文仅供安全研究和学习使用,由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,公众号及文章作者不为此承担任何责任。
突破WAF!帆软模板注入漏洞利用新姿势的更多相关文章
- SSTI服务端模板注入漏洞原理详解及利用姿势集锦
目录 基本概念 模板引擎 SSTI Jinja2 Python基础 漏洞原理 代码复现 Payload解析 常规绕过姿势 其他Payload 过滤关键字 过滤中括号 过滤下划线 过滤点.(适用于Fla ...
- Atlassian JIRA服务器模板注入漏洞复现(CVE-2019-11581)
0x00 漏洞描述 Atlassian Jira是澳大利亚Atlassian公司的一套缺陷跟踪管理系统.该系统主要用于对工作中各类问题.缺陷进行跟踪管理. Atlassian Jira Server和 ...
- Flask(Jinja2) 服务端模板注入漏洞(SSTI)
flask Flask 是一个 web 框架.也就是说 Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序.这个 wdb 应用程序可以使一些 web 页面.博客.wiki.基于 we ...
- SSTI-服务端模板注入漏洞
原理: 服务端模板注入是由于服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而导致了敏感信息泄露.代码执行.GetShell ...
- Commix命令注入漏洞利用
介绍 项目地址:https://github.com/stasinopoulos/commix Commix是一个使用Python开发的漏洞测试工具,这个工具是为了方便的检测一个请求是否存在命令注入漏 ...
- DedeCMS全版本通杀SQL注入漏洞利用代码及工具
dedecms即织梦(PHP开源网站内容管理系统).织梦内容管理系统(DedeCms) 以简单.实用.开源而闻名,是国内最知名的PHP开源网站管理系统,也是使用用户最多的PHP类CMS系统,近日,网友 ...
- 2017年第二届广东省强网杯线上赛WEB:Musee de X writeup(模板注入漏洞)
目录 解题思路 总结 解题思路 拿到手上,有四个页面 首先按照题目要求执行,尝试注册一个名为admin的账户 这种情况,路径都给出来了,很可能就是目录遍历或者文件上传了 回到初始界面,点击链接here ...
- DedeCMS全版本通杀SQL注入漏洞利用代码
EXP: Exp:plus/recommend.php?action=&aid=1&_FILES[type][tmp_name]=\' or mid=@`\'` /*!50000u ...
- Drupal 7.31 SQL注入漏洞利用具体解释及EXP
有意迟几天放出来这篇文章以及程序,只是看样子Drupal的这个洞没有引起多少重视,所以我也没有必要按着不发了,只是说实话这个洞威力挺大的.当然.这也是Drupal本身没有意料到的. 0x00 首 ...
- Flask(Jinja2) 服务端模板注入漏洞
原理 参考文章: https://www.blackhat.com/docs/us-15/materials/us-15-Kettle-Server-Side-Template-Injection-R ...
随机推荐
- AI Agent现实应用与未来展望:从个人到社会的变革(下篇)
认知是成本最低的对冲. --张三思维进化论 从理论到实践:Agent技术落地的关键时刻 在前两篇文章中,我们探讨了AI Agent的概念认知和技术原理: 从"被动对话"到" ...
- 【代码】Python3|无GUI环境中使用Seaborn作图的学习路线及代码(阴影折线图)
我有个需求是需要画图,让GPT帮我生成了一下学习计划. 学习路线依照GPT的来的,使用的Prompt工具是https://github.com/JushBJJ/Mr.-Ranedeer-AI-Tuto ...
- SpringSecurity配置 2
SpringSecurity配置 2 目前的现状,虽然是有了登录认证的接口,但是登录完成后,当我们访问受保护的接口时,即使将 Token 令牌携带与请求一起发送,依然是无法请求成功.另外,提示信息如下 ...
- JVM垃圾回收为什么要分代
分代的垃圾回收策略,是基于不同对象的生命周期不一样: 绝大多数对象都是朝生夕灭; 熬过越多次垃圾收集过程的对象就越难以消亡; 跨代引用相对于同代引用来说仅占极少数. 因此,不同生命周期的对象可 ...
- 在Linux下使用wxWidgets进行跨平台GUI开发(二)
wxWidgets常见辅助类的应用示例 wxWidgets提供了一系列功能强大的辅助类(Helper Classes),涵盖了字符串处理.文件操作.XML解析.数据流.数据库和网络通信等功能,这些类为 ...
- 2025年KOL运营工具深度测评:11款高效工具全面解析,助力品牌全流程管理
在数字营销日益精精化的今天,KOL(关键意见领袖)已成为品牌传播和用户转化的重要力量.然而,面对多平台.多任务的复杂协作需求,如何高效管理KOL资源.优化合作流程.提升ROI,成为品牌促销经理们急需解 ...
- WDA SEARCH step by step
之前写了不少的东西,其实大多数都是给自己看的,我的习惯是把资料放到网上,用的时候直接看博客. 之前硬盘轻轻摔了一下,几年的资料没了,然后就再也不用硬盘了. 昨天有人突然问我关于WDA的问题,毕竟奇怪, ...
- 实测提速 60%!Maven Daemon 全面加速 SeaTunnel 编译打包效率
作者 | 张东浩 在大规模数据集成项目中,构建效率尤为关键.本文实测了 Apache SeaTunnel 项目在使用传统 Maven 与新一代构建工具 Maven Daemon(mvnd)下的打包效率 ...
- Java变量类型识别
方法: 1.反射方式,成员变量的类型判断2.isInstance用法3.利用泛型识别类型一.新建测试类 import java.util.Date; import com.cxyapi.generic ...
- 指标+AI:迈向智能化,让指标应用更高效
近日,以"Data+AI,构建新质生产力"为主题的袋鼠云春季发布会圆满落幕,大会带来了一系列"+AI"的数字化产品与最新行业沉淀,旨在将数据与AI紧密结合,打破 ...