createquery() 用 hql 仍可能被注入,因它不防注入,仅创建查询对象;风险源于字符串拼接(如"name='"+username+"'"),安全做法是用命名参数(:name)或位置参数(?1)并setparameter绑定。

createQuery() 用 HQL 时为什么还会被注入?
因为 createQuery() 本身不防注入——它只负责创建查询对象,真正起作用的是你传进去的字符串是否含拼接。比如写成 "FROM User WHERE name = '" + username + "'",哪怕用了 createQuery(),照样中招。
安全写法必须用命名参数(:param)或位置参数(?1),让 Hibernate 底层走 PreparedStatement 绑定:
-
createQuery("FROM User WHERE name = :name")✅ -
query.setParameter("name", userInput)✅ - 避免任何
+拼接、String.format()、StringBuilder构造 HQL 字符串 ❌
createSQLQuery() 禁止字符串拼接的硬性约束
题目明确禁止原生 SQL 拼接,那 createSQLQuery() 就只能走参数化路径。但注意:它不支持 HQL 那种命名参数语法(:name),必须用 ? 占位符 + 位置绑定,或显式调用 addScalar() 配合 setParameter()。
常见错误:
- 写成
"SELECT * FROM users WHERE id = " + id→ 直接漏洞 - 写成
"SELECT * FROM users WHERE name = ?"+setParameter(0, name)→ 安全(索引从 0 开始) - 若字段名/表名需动态(如分表),不能参数化 → 必须白名单校验,例如
if (!Arrays.asList("log_2025", "log_2026").contains(tableSuffix)) throw new IllegalArgumentException();
Criteria API 是最省心的防御方式
不用写字符串,天然杜绝拼接风险。Hibernate 5+ 的 JPA Criteria API 或 Hibernate 自己的 CriteriaBuilder 都是类型安全、编译期可检的。
示例:
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<user> cq = cb.createQuery(User.class);
Root<user> root = cq.from(User.class);
cq.select(root).where(cb.equal(root.get("username"), username));
List<user> users = session.createQuery(cq).getResultList();</user></user></user>
关键点:
- 所有字段名、条件逻辑都通过 Java 方法链表达,没字符串参与
- 参数值仍需传入变量(如
username),但已脱离 SQL 文本上下文 - 性能略低于手写 HQL,但对绝大多数业务查询影响可忽略
哪些地方容易漏掉而引入风险?
最常被忽视的不是主查询,而是动态条件组装、排序字段、分页 offset/limit 的拼接。
例如:
-
ORDER BY " + sortField→ 必须白名单限制:if (!Set.of("name", "created_time", "status").contains(sortField)) throw ... -
setFirstResult(Integer.parseInt(offsetStr))→ 先校验是否为非负整数,再转,别直接 parse - HQL 中用
IN子句传集合?用setParameterList("ids", idList),别手动拼(1,2,3) - 使用
@NamedQuery时,XML 或注解里的 HQL 也得检查有没有+拼接逻辑(比如在 service 层生成完再塞进注解)
参数化不是一劳永逸的开关,是每个字符串参与 SQL 构建的节点都要过一遍校验。动态性越强的地方,白名单或类型约束越不能少。











