导读:本期聚焦于小伙伴创作的《为什么有些ORM框架仍会产生SQL注入?避免使用框架提供的原生查询接口真的够吗》,敬请观看详情。把用户输入直接拼进字符串再交给ORM执行原生SQL,是引发注入的典型误区。不少团队以为用了对象关系映射就天然安全,却在调用框架暴露的raw、createSQLQuery之类接口时手动拼接参数。底层数据库只认最终发出的语句文本,拼接过程若未做绑定变量,攻击者构造的闭合并添加逻辑的条件就会改变语义。以Hibernate与MyBatis为例,前者若用Session.createSQLQuery拼字符串,后者若写${}占位符,都会绕过预编译保护。真正稳妥的做法是统一走参数化查询,对必须写原生SQL的场景做白名单校验与严格转义,而非简单禁用接口了事。

对象关系映射框架本意是屏蔽SQL细节、降低出错概率,但在实际项目中仍然频繁出现借助ORM发起的SQL注入。根本原因往往不在于框架本身有缺陷,而是开发者在追求灵活查询时,绕开了框架的参数绑定机制,使用其暴露的原生查询能力直接拼接字符串。即便主流ORM都提供了预编译与占位符功能,一旦落入手工拼语句的陷阱,数据库执行层就无法区分代码与数据。

为什么有些ORM框架仍会产生SQL注入?避免使用框架提供的原生查询接口真的够吗

原生查询接口为何成为注入突破口

几乎所有ORM都会提供一种“ escape hatch”式的原生查询入口,例如Hibernate的createSQLQuery、Entity Framework的FromSqlRaw、Django的raw。这些接口的存在是为了弥补框架DSL在复杂报表、数据库特有函数上的不足。问题在于,它们通常既支持参数绑定,也允许传入一段完整SQL字符串。当开发者图省事将前端参数直接并入字符串,就等同于在JDBC的Statement上做字符串拼接。

从数据库协议角度看,无论SQL是由ORM生成还是手写,最终都以文本形式发往服务端解析。若文本中混入了用户输入且未用占位符隔离,解析器便会将数据当作命令的一部分。比如查询条件中原本应是where name = '张三',攻击者传入张三' or '1'='1,拼接后变成两条逻辑条件的或关系,返回了全表数据。ORM在此场景里只是传声筒,并没有任何自动清洗能力。

常见框架的危险写法示例

下面以Hibernate为例,展示一段容易产生注入的代码。注意其中使用字符串拼接而非参数绑定:

// 错误示例:直接拼接用户输入
String userInput = request.getParameter("name");
String sql = "select * from users where name = '" + userInput + "'";
SQLQuery query = session.createSQLQuery(sql);
List result = query.list();

上述代码在userInput包含单引号与逻辑运算符时会改变SQL语义。相对安全的写法应当使用命名参数,让框架在底层调用PreparedStatement:

// 正确示例:使用命名参数绑定
String sql = "select * from users where name = :name";
SQLQuery query = session.createSQLQuery(sql);
query.setParameter("name", userInput);
List result = query.list();

MyBatis中类似的风险来自${}#{}的差异。前者做字符串替换,后者生成占位符。若排序字段使用order by ${column}且column来自前端,就构成了注入点。开发时应尽量用#{},对必须动态传入的标识符采用服务端白名单校验。

仅禁用原生接口并不能根除风险

有些团队规定“禁止调用任何原生查询接口”,希望借此杜绝注入。这种做法在理论上减少了危险入口,但实际业务里往往难以执行。报表统计、地理空间查询、窗口函数等需求,ORM的封装要么不支持要么性能极差,最终开发人员会偷偷用字符串拼接的存储过程或外部SQL文件绕过规范。

更关键的是,即便只使用ORM的封装方法,如果误用API仍可能中招。例如Hibernate的Restrictions.sqlRestriction允许写片段SQL,若里面拼了变量同样危险;JPA的CriteriaBuilder虽类型安全,但一旦用function嵌入原生函数并拼参数,风险回归。因此安全的核心不是避开某个接口,而是建立“任何外部输入都必须通过绑定或校验”的编码习惯。

建立参数化与校验的双重机制

对于必须使用原生SQL的地方,建议封装统一的执行器,内部强制使用占位符,并对非值类型的输入(如表名、列名)做枚举白名单。如下简表列出不同输入类型的处理方式:

输入用途处理方式说明
查询值(where条件)参数绑定使用?或命名参数,禁止拼接
排序字段白名单映射前端传数字编号,后端映射为真实列名
表名配置常量不允许用户直接指定,从服务端配置读取

在代码评审环节,应当把“是否出现字符串加号连接SQL”作为红线规则。静态扫描工具也可以识别createSQLQuery(后面跟随变量拼接的模式并报警。只有将框架能力、规范约束与工具检查结合,才能在保留原生查询灵活性的同时控制注入风险。

从架构层面降低出错概率

除了编码习惯,架构设计也能减少人为失误。比如将复杂查询统一收口到只读服务,由专门的数据访问层提供方法签名,业务代码只能传参不能写SQL。再如采用GraphQL或结构化查询构建器,在边缘层就把用户输入转换成抽象语法树,再序列化为参数化语句。

总之,ORM产生SQL注入不是因为框架靠不住,而是原生接口被当作普通字符串函数使用。避开原生查询接口是一种简单粗暴的缓解手段,但真正可持续的方案是理解数据库协议边界、坚持参数化并约束动态标识符。这样即便不得不写原生SQL,也能把风险关进笼子里。

ORMSQL注入原生查询接口修改时间:2026-07-31 15:39:28

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。