SQLFix 博客
参数化查询与生成的SQL
一个能够生成看似正确的查询的模型并没有解决注入问题。真正重要的安全属性存在于查询如何与数据交互,这是参数化查询和预备语句的职责。生成的文本只是审查的起点;它不能替代预备语句在代码和数据之间强制执行的边界。
准备语句实际上做了什么
当驱动程序接收到准备请求时,它将 SQL 文本发送到服务器。服务器解析文本,构建执行计划,并存储该计划。稍后,当应用程序调用 execute 时,驱动程序仅发送参数值。服务器将存储的计划应用于这些值,而不重新解析 SQL 文本。因为值永远不会成为语句字符串的一部分,因此诸如 ' OR 1=1-- 之类的有效载荷被视为字面值,而不是命令的一部分。
我已经在 MySQL 5.0、PostgreSQL 8.0、SQL Server 2005 和 Oracle 9i 中看到过这种工作。每种主流语言都提供了绑定 API:JDBC 的 PreparedStatement、Python 的 cursor.execute带占位符、Go 的 database/sql.Query 和 Node 的 mysql2 pool.execute。模型可以输出形状 SELECT * FROM orders WHERE customer_id = ? AND status = ? ; 周围的代码在运行时提供两个值。
如果你在语句文本中看到引号,你就不是在使用真正的准备语句;你可能正在进行客户端字符串插值,这不会提供注入安全性。
为什么经过审查的SQL仍需要参数
人工审查可以确认查询的形状是否与预期的业务规则相匹配。它可以发现缺失的连接、错误的列名或低效的过滤器。然而,流入这些占位符的值仍然来自外部源:Web 表单、API有效载荷、消息队列或遗留文件。这些源在执行时刻不在开发人员的直接控制之下。
OWASP的SQL注入防御备忘录明确指出:“防御选项1:预备语句(参数化查询)”。备忘录中没有将“人工审批”列为替代方案。审批解决逻辑正确性问题;参数化解决代码和数据之间的信任边界问题。放弃任意一方都会使另一方暴露在外。
即使人工审查员看到一个合理的INSERT语句,注入也发生在值绑定步骤,这是审查没有涵盖的。
这样,审查员的工作仍然有价值,而执行步骤保留了其安全保证。
存储过程并非自动安全
许多团队假设将逻辑移到存储过程中可以消除注入风险。然而,当存储过程本身构建动态SQL时,这种假设就会失败。
考虑一个用T‑SQL编写的存储过程:
CREATE PROCEDURE GetOrders @Customer NVARCHAR(50) AS BEGIN DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Orders WHERE Customer = ''' + @Customer + ''''; EXEC sp_executesql @sql; END 如果@Customer包含'); DROP TABLE Orders;--,则生成的字符串变成一个有效的批处理,会删除表。存储过程包装器并没有保护数据;存储过程内部的拼接导致了问题。
同样的模式出现在PL/SQL中使用EXECUTE IMMEDIATE,PostgreSQL中使用EXECUTE format,以及MySQL中使用PREPARE/EXECUTE从变量构建的语句中。
在重写后,使用包含引号的值运行存储过程,并验证错误是数据类型不匹配,而不是语法错误。
将生成和执行分为独立的层
最安全的架构将 AI 模型视为建议引擎,而不是数据访问组件。模型输出一个候选查询形状。人类或自动化策略门keeper 审查该形状的正确性、合规性和性能。只有在批准后,应用程序代码才会获取该形状,使用驱动程序准备它,并绑定实时参数。
因为模型永远不会看到实时连接,所以它无法被欺骗以通过错误消息泄露模式细节。因为执行层永远不会信任原始字符串,即使是损坏的模型也无法引起注入。
此过程将模型的输出保持在审查循环中,并保证执行步骤仅使用参数化语句。
这不是什么
我想明确说明上述方法的局限性。它不能保证捕获每一个可能的漏洞。它不能保证100%地保护所有类型的缺陷。它也不声称消除对其他防御措施的需求,例如输入验证、输出编码或最小特权数据库账户。
该方法的作用是:
如果您仅依赖模型的输出并跳过审查或参数绑定步骤,您将失去保护。如果您允许执行层在准备后连接值,您将重新引入风险。该技术是深度防御策略的一个组件,而不是独立的银弹。
实际的工作流程示例
想象一个允许经理导出逾期发票列表的SaaS产品。该产品使用SQLForm来草拟查询。
此跟踪结果显示了生成步骤如何保持与执行步骤的独立性,以及参数边界如何在用户尝试将SQL注入模型永远不会看到的字段时阻止注入。
常见的陷阱以及我们如何避免它们
即使拥有坚实的设计,团队有时也会陷入削弱保护的习惯。以下是我观察到的常见错误以及我们采取的具体步骤来保持系统安全。
一些开发人员通过首先创建一个带有正确数量的占位符的字符串,然后直接将值连接到SQL中,然后再准备SQL来构建SELECT * FROM tags WHERE tag IN (?, ?, ?)。这破坏了目的,因为值成为语句文本的一部分。
为SELECT * FROM orders WHERE status = ?准备的语句稍后使用包含整个新SELECT语句的值执行,依赖于驱动程序将值视为数据。虽然值被安全地引号括起来,但应用程序逻辑可能是错误的,因为开发人员假设该语句可以处理任何SQL。
内部仪表板有时会绕过人工审查门,因为团队假设数据是可信的。稍后,服务帐户被泄露并开始向仪表板提供恶意有效载荷。
为了调试,团队有时会记录确切的参数值。如果这些日志被暴露,它们可以泄露敏感数据,例如密码或个人可识别信息。
ORM可能提供一个看似接受参数的方法,但在底层它构建一个字符串并在原始SQL字符串上调用ExecuteNonQuery。
通过监视这些陷阱并应用相应的修复,生成和执行之间的分离保持强大,参数化边界继续发挥其设计的作用。
参考资料
https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
https://cwe.mitre.org/data/definitions/798.html
https://cwe.mitre.org/data/definitions/89
常见问题
不。注入取决于值如何满足查询,而不是查询是否在逻辑上正确。参数化查询在值内容无关紧要的情况下保持值与命令文本分离。
不是自动的。一个连接输入并使用EXECUTE IMMEDIATE执行的过程仍然可以注入。安全来自过程内部的参数绑定。
ORM只有在使用其参数化接口时才是安全的。构建字符串并将其传递给ORM会重新引入注入。代码和数据之间的边界仍然必须被执行。
不。保持生成和执行分离:模型草稿,门槛审批,应用程序代码在运行时绑定参数。模型永远不应该是执行路径。
尝试产品:SQLForm
2026-09-16 · 只有主要来源 · 没有伪造的流量指标
相关文章
自然语言到SQL:重要的安全检查
探索完整的AI微SaaS主题矩阵 →
正确的查询是否可以保护免受SQL注入?
不。注入取决于值如何满足查询,而不是查询是否在逻辑上正确。参数化查询可以在不考虑值内容的情况下将值与命令文本分开。
存储过程是否默认安全?
不是自动的。一个过程如果将输入与命令文本连接并使用EXECUTE IMMEDIATE执行,仍然可能注入。安全性来自于过程内部的参数绑定。
ORM是否可以替代参数化查询?
ORM只有在使用其参数化接口时才是安全的。构建一个字符串并将其传递给ORM会重新引入注入。代码和数据之间的边界仍然需要被强制执行。
模型是否应该持有数据库连接?
不。保持生成和执行分离:模型草拟,网关批准,应用程序代码在运行时绑定参数。模型永远不应该是执行路径。
仅供决策参考,不替代DBA审查或生产负载测试。无性能保证。