动态SQL拼接在ERP中是高危注入区,字段名、表名、排序字段必须白名单校验,WHERE条件需分层过滤(字段/运算符校验+值参数化),ORDER BY和分页参数须严格限制合法值及范围。

动态SQL拼接是ERP里最常被忽略的注入高危区——尤其在自定义查询、报表导出、高级筛选这类功能中,90%以上的漏洞不是出在登录框,而是出在“按条件搜索”这个看似无害的输入框里。
为什么自定义查询比普通表单更容易出问题
因为这类功能天然需要“拼SQL”:用户选字段、选运算符(=、LIKE、IN)、填值,后端用字符串拼出完整语句。开发常误以为“用户只能选下拉项”,但实际攻击者可直接发请求绕过前端限制;更危险的是,很多ERP把字段名、表名、排序字段也做成可配置项,而这些根本不能参数化。
- 字段名/表名/排序字段必须白名单校验,
ORDER BY后的column_name不能用参数占位符 - 运算符(如
LIKE、BETWEEN)必须从固定枚举中取,禁止传入OR 1=1这类字符串 - 值部分才能用参数化,但若前端允许用户输入 SQL 片段(如“自定义表达式”),哪怕用了
PreparedStatement也拦不住
如何安全地拼接 WHERE 条件
不要写 "WHERE " + field + " " + op + " '" + value + "'"。正确做法是分层过滤:
- 字段名查白名单:
if (!allowedFields.contains(fieldName)) throw new SecurityException("非法字段"); - 运算符只允许
IN、=、LIKE等有限几个,且LIKE的值必须前后加%由代码控制,不交给用户填 - 值部分统一走参数化:
ps.setString(1, userInputValue),哪怕字段是数字也要用setInt而非拼字符串 - 多条件 AND/OR 组合时,用
StringBuilder拼条件片段,但每个片段的值都绑定独立参数,避免一个参数复用多次
排序和分页字段怎么处理
ORDER BY 和 LIMIT/OFFSET 是重灾区。数据库驱动不支持对排序字段做参数化,所以必须严格白名单。
- 排序字段只允许从预设列表取:
Arrays.asList("create_time", "amount", "status"),传入其他值直接拒绝 - 排序方向(ASC/DESC)同样只接受枚举值,禁止拼接
"ORDER BY " + col + " " + dir - 分页参数
page、size必须转成整型并做范围限制(如size≤ 100),防止LIMIT 1,999999999导致全表扫描 - PostgreSQL 的
OFFSET若过大,会拖慢查询——这不是安全问题,但可能被用来 DoS,需监控慢查询日志
容易被绕过的“伪修复”陷阱
很多团队加了过滤却仍被攻破,问题出在信任链断裂:
- 前端 JS 做了字段过滤,但后端没二次校验——攻击者删掉 JS 或直发 POST 就绕过
- 用了 MyBatis 的
${}替换字段名,却没配bind或白名单拦截器,等于把门钥匙交给用户 - 日志模块从
X-Forwarded-For取 IP 写库,结果这个头被当成“自定义条件”混进查询逻辑,变成注入入口 - 测试时只验证正常流程,没试
dict_key=1' UNION SELECT password FROM sysusers--这类 payload
真正有效的修复,必须让字段名、运算符、排序字段这三类无法参数化的部分,在进入 SQL 构建前就完成白名单校验,且校验逻辑不可绕过。任何依赖前端约束、中间件过滤或 WAF 规则的做法,都只是纸面防御。

















