参数化查询是批量导入唯一安全路径,须用preparedstatement逐条addbatch;动态表名需白名单校验;导入前强制类型校验与字段约束;执行上下文须权限隔离。

批处理导入时,只要用了字符串拼接构造 SQL,哪怕只有一条记录含恶意内容,整批都会变成注入入口——参数化查询不是“可选优化”,是唯一安全路径。
用 PreparedStatement 批量执行而非拼接 INSERT 语句
常见错误是把一批数据循环拼成一条大 SQL,例如 INSERT INTO users VALUES ('a','b'),('c','d'),...;。一旦某条数据含 ' OR 1=1 --,整条语句就失控。
正确做法是复用同一个 PreparedStatement 实例,逐条 addBatch() + executeBatch():
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
for (User u : userList) {
stmt.setString(1, u.getName()); // 自动转义,不参与语法解析
stmt.setString(2, u.getEmail());
stmt.addBatch();
}
stmt.executeBatch();
- 每条记录都走参数绑定,数据库在预编译阶段就锁定了语句结构
- 避免手动拼接引号、逗号、括号,也绕开了
addslashes()或mysql_real_escape_string()的宽字节/编码绕过风险 - 注意:
executeBatch()前必须调用setAutoCommit(false)并在成功后commit(),否则可能部分写入
动态表名或字段名必须走白名单校验
批量导入常需适配不同业务表(如 order_2026、log_web),但 PreparedStatement 不支持参数化表名——"INSERT INTO ? VALUES (?)" 会直接报错 SQLSTATE[HY093]。
此时必须将用户输入映射到服务端硬编码的白名单:
Map<string string> tableWhitelist = Map.of(
"orders", "order_2026",
"logs", "log_web"
);
String tableName = tableWhitelist.get(inputTableKey);
if (tableName == null) throw new IllegalArgumentException("Invalid table key");</string>
- 禁止用
String.format("INSERT INTO %s", userInput)或正则替换 - 白名单键值对要定义在配置类或枚举中,不可从配置文件动态加载(防篡改)
- 若需支持分库分表,路由逻辑应基于主键哈希或时间戳等确定性规则,而非用户可控字段
CSV/Excel 导入时警惕“隐式类型转换”漏洞
当用 JdbcTemplate.batchUpdate() 或 MyBatis <foreach></foreach> 批量插入时,如果传入的是 List<map object>></map>,且某字段值为字符串 "1' OR '1'='1",而数据库列是 INT 类型,MySQL 可能静默截断或转换,导致绕过参数检查。
关键控制点:
- 导入前强制校验每列数据类型:用
Integer.parseInt()验证数字字段,失败则拒收整行 - 数据库表设计时,明确字段
NOT NULL和长度限制(如VARCHAR(64)),超长字段会被截断,可能切掉闭合引号 - 避免用
Object作为参数泛型;MyBatis 场景下优先用具体 DTO 类,让 Jackson/Gson 反序列化时就抛异常
最易被忽略的一点:批量导入接口往往没做权限隔离,攻击者上传一个含恶意 payload 的 CSV,再触发定时任务执行,就能绕过常规 Web 请求过滤器——所以导入任务的执行上下文(如线程池、事务管理器)必须和普通 API 完全隔离,且日志要记录原始文件哈希与操作人 ID。











