文章总结: 这篇文章分析了帆软export/excel接口的SQL注入漏洞,实际上是帆软内置函数和表达式执行的问题。文章详细介绍了漏洞触发点在LargeDatasetExcelExportHandler的dealParam方法中,通过控制evalValue参数可执行任意表达式。作者提供了参数构造方法和利用流程,包括如何构造functionParams、params等参数来触发漏洞,但出于安全考虑未提供完整EXP。
综合评分: 87
文章分类: 漏洞分析,漏洞POC,WEB安全
帆软export/excel SQL注入漏洞分析及复现
原创
Y5neKO
Y5Sec
2025年12月22日 10:02
四川
官方通告
https://help.fanruan.com/finereport/doc-view-4833.html
从官方通告我们可以知道漏洞点在某个 export/excel 接口
漏洞分析
我们这里以11.0版本为例
一共找到两个版本,分别调用的Handler如下:
com.fr.nx.app.web.handler.export.largeds.LargeDatasetExcelExportHandler
com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler
分别跟进看了一下,解析的逻辑大差不差
暂且不管,随便调一个往下看,这里先看v9
官方说是SQL注入漏洞,那么我们重点关注sql相关语句,跟到doHandle方法下个断点
前两行分别是从请求当中的sessionID来获取一个模板对象以及对应的Calculator,主要是用于处理各种公式运算
这里的sessionID是每个表格每次导出所对应的sessionID值,随便导出一个示例表格可以得到
随后跟进initCreator方法
该流程一共需要三个参数(排除sessionID):
_parameters_
从参数中获取,json格式,结构满足如下条件:
params
从参数中获取,xml格式,结构满足如下条件:
functionParams
从参数中获取,json格式,结构满足如下条件:
可以看到这3个参数都是可控的,而其中涉及到SQL查询的地方在下面位置
com.fr.io.exporter.excel.direct.WorkbookDataCreator#build
com.fr.io.exporter.excel.direct.WorkbookDataCreator#init
而init方法中有个很熟悉的字眼:renderSql方法,跟进
这个方法是某个nday中的漏洞点,可以处理一些帆软内置的函数,其中第二个参数为要处理的字符串,按道理来说只要能控制this.tableData.getQuery()的值就能执行内置函数,但我们跟进到这里会发现,query的值是不可控的
这里的TableData只能从数据源中获取,而数据源我们只能控制dsName,所以这里这个方法是用不了的,那么还有哪里可以控制呢
回头看还有一个熟悉的字眼:
com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler#dealParam
巧了,正好就是某nday的触发点
com.fr.script.Calculator#evalValue(java.lang.String)
刚刚我们提到com.fr.script.Calculator是处理公式运算,其中包含一些帆软内置函数的处理,这里使用的是帆软自己定义的一套表达式引擎
继续跟进
通过parse方法对表达式进行解析,最后查找到对应的function类来进行处理
而我们从官方文档可以知道,帆软有个内置的sql函数可以用来执行语句,因此我们只需要控制这里的evalValue参数即可
到这里我们其实可以看出来,这个漏洞并不像真正意义上的sql注入,而是帆软内置函数和表达式执行的问题,下面我们尝试利用
利用流程
控制evalValue参数
刚刚分析了整个流程,回头看一下怎么控制evalValue的参数
com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler#dealParam
共有两种情况,都与var17有关,而var17的来源是var3.getParameters(),即LargeDatasetExcelExportJavaScript.getParameters(),往回溯源
即从params参数中获取,也就是我们最开始分析到的3个参数,我们依次看一下怎么构造
__functionParams__构造
没什么特殊的要求,满足json格式即可:{}
functionParams和params构造
这里我们需要综合考虑,以及两种情况
第一种情况:
令var19为null,走if分支,还要保证var17的值是Formula的实例,这一步比较麻烦,所以我们优先考虑下面情况
第二种情况:
令var19不为null,走else分支,只需要让var17通过replaceAll处理后就可以执行
那么这种情况下的functionParams和params需要满足:
JSONObject var19 = (JSONObject)var8.get(var17.getName());
- • var8中存在键名和var17.getName()同名的键值对,并且键值也要满足json格式,不能为null
- • var17是遍历LargeDatasetExcelExportJavaScript实例中的parameters,所以xml需要存在Parameters节点
functionParams
满足json格式随便给个键值对:{“p”:{“x”:2}}
params
通过LargeDatasetExcelExportJavaScript获取parameters,而参数是在下面位置设置的
readXMLObject方法是一个用状态机 + 回调的方式处理xml格式数据的框架方法,用于处理xml各个节点的数据,我们可以通过帆软本身的GeneralXMLTools.writeXMLableAsString方法来生成
import com.fr.base.Formula;
import com.fr.base.Parameter;
import com.fr.general.GeneralUtils;
import com.fr.general.xml.GeneralXMLTools;
import com.fr.nx.app.web.handler.export.largeds.bean.LargeDatasetExcelExportJavaScript;
import com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler;
import com.fr.stable.FormulaProvider;
import com.fr.stable.ParameterProvider;
import com.fr.stable.bridge.StableFactory;
import com.fr.base.Formula;
import com.fr.base.Parameter;
import com.fr.general.xml.GeneralXMLTools;
import com.fr.nx.app.web.handler.export.largeds.bean.LargeDatasetExcelExportJavaScript;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class Test {
public static void main(String[] args) {
Parameter p = new Parameter();
p.setName("p");
p.setValue("=1+1");
LargeDatasetExcelExportJavaScript js = new LargeDatasetExcelExportJavaScript();
js.setDsName("XXX");
js.setParameters(new Parameter[]{p});
String xml = GeneralXMLTools.writeXMLableAsString(js);
System.out.println(xml.replace("\\", ""));
System.out.println(urlEncode(xml));
}
public static String urlEncode(String str) {
str = str.replace("\\", "");
try {
return URLEncoder.encode(str, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return str;
}
}
}
其中Parameter中的name值和我们前面给的functionParams的值对应(p),内容为一个表达式(=1+1),dsName没有要求,默认有个ds1,实测随便传都可以
请求包构造
总结一下我们的三个参数以及sessionID:
sessionID:自行生成或通过sql注入获取
functionParams={"p":{"x":2}}
__parameters__={}
params:满足条件的XML
构造请求包如下:
GET //webroot/decision/nx/report/v9/largedataset/export/excel?functionParams=%7B%22p%22%3A%7B%22x%22%3A2%7D%7D&__parameters__={} HTTP/1.1
Host: xxxx
Cookie: JSESSIONID=3CB53ED924B36AF3EFA536936406EE64; tenantId=default; fine_remember_login=-2; fine_auth_token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudElkIjoiZGVmYXVsdCIsImlzcyI6ImZhbnJ1YW4iLCJkZXNjcmlwdGlvbiI6ImFkbWluKGFkbWluKSIsImV4cCI6MTc2NzU0ODM4MywiaWF0IjoxNzY2MzM4NzgzLCJqdGkiOiJ1MnZFRmxaSlVnR0Q4WFU1SUxHK0ZqWU1Xc1pwNnBrVEpCVjllRE5hZ3o0UC81NkgifQ.X9Z3vswB_4XSNafAqlAc_MOKypxUvBzfQesfXnxxRic; sessionID=cad7b49e-0730-4443-9387-48851805943d
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInRlbmFudElkIjoiZGVmYXVsdCIsImlzcyI6ImZhbnJ1YW4iLCJkZXNjcmlwdGlvbiI6ImFkbWluKGFkbWluKSIsImV4cCI6MTc2NzU0ODM4MywiaWF0IjoxNzY2MzM4NzgzLCJqdGkiOiJ1MnZFRmxaSlVnR0Q4WFU1SUxHK0ZqWU1Xc1pwNnBrVEpCVjllRE5hZ3o0UC81NkgifQ.X9Z3vswB_4XSNafAqlAc_MOKypxUvBzfQesfXnxxRic
Accept-Language: zh-CN,zh;q=0.9
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
sessionID: cad7b49e-0730-4443-9387-48851805943d
Accept-Encoding: gzip, deflate
params: xxxxxxxxxxxxx
下断点发包
跟进dealParam
此时var19不为null,进入else分支,获取到的表达式为:=1+1
成功执行普通表达式,使用带内置函数的表达式方法类似,例如sql函数:
过滤
在表达式执行的过程中,如果涉及到sql执行,会进行单独的校验处理
com.fr.cbb.dialect.security.JDBCSecurityChecker#check
处理方式大家参考某nday就行了
复现
EXP出于某些已知原因就不给出,xdm可以自己审一下
声明
本文章仅供安全研究和学习,使用本文章内容造成的后果由使用者自行承担。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Y5Sec Y5neKO《帆软export/excel SQL注入漏洞分析及复现》