JeecgBoot积木报表queryFieldBySql接口因未授权且直接将用户输入的SQL交由FreeMarker解析,导致模板注入,进而触发RCE(CVE-2023-4450),影响JimuReport<1.6.1版本。
为什么报表系统里用存储过程也挡不住SQL注入
因为存储过程本身不免疫SQL注入——只要你在过程内部拼接用户输入,比如
或
且未校验参数内容,攻击者仍能通过
这类接口注入恶意语句。JeecgBoot 积木报表就曾因此暴露出远程代码执行风险。
动态表名/字段排序必须用白名单,不能靠过滤字符
报表常需支持“按用户选的字段排序”或“查不同业务表”,此时
或
是高危点。正则过滤
、
等关键字容易被绕过(比如大小写变形、编码混淆、注释干扰),真正可靠的做法是:
预先定义合法值集合,如
校验输入是否严格等于其中某一项,不匹配则拒绝请求
避免任何字符串拼接逻辑,连
或
匹配都不要用
存储过程权限要收缩到最小,且禁止跨库调用
即使用了白名单,存储过程若以高权限账户(如
、
)执行,仍可能被用于读取系统表、写文件甚至执行系统命令(如 MySQL 的
)。修复时必须:
防SQL注入的php类库
防SQL注入的php类库
下载
为报表专用创建独立数据库用户,仅授予
权限在指定存储过程上
显式拒绝该用户对
、
、
等系统库的访问
存储过程内部禁用
(SQL Server)、
(MySQL)等高危函数
MyBatis + 存储过程组合下最容易漏掉的坑
很多人以为“用了存储过程 +
就安全了”,但实际漏洞常出在调用层:
中若传入
,MyBatis 会报错——因为存储过程参数不支持动态表名
于是开发者退而求其次,改用
拼进
,直接破防
正确做法是:把白名单校验提前到 Java 层,校验通过后再用
传入存储过程(作为普通参数,非 SQL 结构)
动态SQL的安全边界不在数据库侧,而在“谁决定结构、谁控制流向”。白名单必须落在调用前的代码层,且不可被运行时绕过——哪怕只漏一个排序字段,也可能成为整个系统的入口点。
EXEC(@sql)sp_executesql @sqlqueryFieldBySql${sortField}${tableName}unionsleep["user_name", "create_time", "status"]INSTR()LIKEsarootSELECT ... INTO OUTFILEEXECUTEinformation_schemamysqlsysOPENROWSETLOAD_FILE#{}#{tableName}${tableName}CALL proc_name('${tableName}')#{safeTableName}