PreparedStatement 如何避免或防止 SQL 注入?

How does a PreparedStatement avoid or prevent SQL injection?

提问人:Prabhu R 提问时间:10/17/2009 最后编辑:Josh CorreiaPrabhu R 更新时间:12/23/2021 访问量:132915

问:

我知道 PreparedStatements 避免/防止 SQL 注入。它是如何做到的?使用 PreparedStatements 构造的最终表单查询是字符串还是其他方式?

java sql jdbc 准备语句 sql 注入

评论

3赞 Tom Hawtin - tackline 10/17/2009
从技术上讲,JDBC 规范并不坚持没有 SQL 注入缺陷。我不知道有任何受影响的驱动器。
4赞 Pavan Manjunath 12/6/2015
@Jayesh我建议在此处添加您的博客内容作为答案。大多数答案只是告诉了动态 SQL 查询生成和准备好的 stmt 的区别。他们没有解决为什么准备好的陈述效果更好,而你的博客却做到了。
1赞 Jayesh 12/7/2015
添加作为答案,我希望它有所帮助。

答:

88赞 tangens 10/17/2009 #1

SQL 注入的问题在于,用户输入被用作 SQL 语句的一部分。通过使用预准备语句,可以强制将用户输入作为参数的内容(而不是 SQL 命令的一部分)进行处理。

但是,如果您不使用用户输入作为预准备语句的参数,而是通过将字符串连接在一起来构建 SQL 命令,那么即使使用预准备语句,您仍然容易受到 SQL 注入的影响

评论

1赞 tangens 10/18/2009
当然,但您仍然可以对部分或全部参数进行硬编码。
18赞 david blaine 4/25/2013
请举例说明 - 但是,如果您不使用用户输入作为预处理语句的参数,而是通过将字符串连接在一起来构建 SQL 命令,那么即使使用预处理语句,您仍然容易受到 SQL 注入的影响。
6赞 beldaz 9/25/2013
FWIW 预准备语句不是 JDBC 的东西,而是 SQL 的东西。您可以从 SQL 控制台中准备和执行准备好的语句。PreparedStatement 只是在 JDBC 中支持它们。
226赞 Paul Tomblin 10/17/2009 #2

考虑做同样事情的两种方法:

PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();

PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();

如果“user”来自用户输入,并且用户输入是

Robert'); DROP TABLE students; --

然后,首先,你会被灌水。在第二种情况下,您将是安全的,并且 Little Bobby Tables 将为您的学校注册。

评论

8赞 Max 10/17/2009
因此,如果我做对了,第二个示例中将要执行的查询实际上是: INSERT INTO student VALUES(“Robert');DROP TABLE 学生;--“) - 或者至少类似的东西。这是真的吗?
22赞 Paul Tomblin 10/17/2009
不,首先,你会得到那个声明。在第二条中,插入“Robert”);DROP TABLE students;--“添加到用户表中。
4赞 Max 10/17/2009
这就是我的意思,在第二个例子(“安全”的例子)中,字符串 Robert');DROP TABLE 学生;-- 将保存到 student 表的字段中。我写了别的东西吗?;)
8赞 Paul Tomblin 10/17/2009
对不起,嵌套引号是我尽量避免的,因为这样的混淆。这就是为什么我喜欢带参数的 PreparedStatements。
0赞 beldaz 9/25/2013
当然,Bobby Tables 技巧只适用于允许多行语句的系统。SQL Server 2008 允许使用它们,但 Oracle 不允许。更常见的问题是 WHERE 子句的损坏。
4赞 shahkalpesh 10/17/2009 #3

我想这将是一个字符串。但是输入参数将被发送到数据库,并且在创建实际的SQL语句之前将应用适当的转换/转换。

举个例子,它可能会尝试看看 CAST/Conversion 是否有效。
如果它有效,它可以从中创建一个最终声明。

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

尝试使用接受数值参数的 SQL 语句的示例。
现在,尝试传递一个字符串变量(其数值内容可以作为数值参数接受)。它会引起任何错误吗?

现在,尝试传递一个字符串变量(其内容不能作为数值参数)。看看会发生什么?

29赞 Travis Heseman 10/17/2009 #4

PreparedStatement 中使用的 SQL 在驱动程序上预编译。从那时起,参数将作为文本值发送到驱动程序,而不是 SQL 的可执行部分;因此,不能使用参数注入任何 SQL。PreparedStatements(预编译 + 仅发送参数)的另一个有益的副作用是,即使参数值不同(假设驱动程序支持 PreparedStatements),多次运行语句时也提高了性能,因为驱动程序不必在每次参数更改时执行 SQL 解析和编译。

评论

0赞 Tom Hawtin - tackline 10/17/2009
它不必像那样实现,我相信它通常不是。
5赞 beldaz 9/25/2013
实际上,SQL 通常是在数据库上预编译的。也就是说,在数据库上准备执行计划。执行查询时,将使用这些参数执行计划。额外的好处是,可以使用不同的参数执行相同的语句,而无需查询处理器每次都编译新计划。
3赞 Guru R Handa 9/29/2011 #5

准备好的语句更安全。它会将参数转换为指定类型。

例如,将参数转换为 String。stmt.setString(1, user);user

假设该参数包含一个包含可执行命令的 SQL 字符串:使用预准备语句将不允许这样做。

它添加了元字符(又名自动转换)。

这使得它更安全。

1赞 Mukesh Kumar 11/18/2015 #6

准备声明:

1) SQL 语句的预编译和数据库端缓存可以加快整体执行速度,并能够批量重用相同的 SQL 语句。

2)通过内置引号和其他特殊字符的转义功能,自动防止SQL注入攻击。请注意,这要求您使用任何 PreparedStatement setXxx() 方法来设置值。

165赞 Jayesh 12/7/2015 #7

要了解 PreparedStatement 如何防止 SQL 注入,我们需要了解 SQL 查询执行的各个阶段。

1. 编译阶段。 2. 执行阶段。

每当 SQL Server 引擎收到查询时,它都必须经过以下阶段:

Query Execution Phases

  1. 解析和规范化阶段:在此阶段,将检查 Query 的语法和语义。它检查引用表和 查询中使用的列是否存在。 它还有许多其他任务要做,但我们不详细介绍。

  2. 编译阶段:在此阶段,查询中使用的关键字(如 select、from、where 等)将转换为格式 机器可以理解。 这是解释查询并决定要采取的相应操作的阶段。 它还有许多其他任务要做,但我们不详细介绍。

  3. 查询优化方案:在此阶段,将创建决策树,以查找可以执行查询的方法。 它找出可以执行查询的方式数量以及与每种方式相关的成本 执行查询。 它选择执行查询的最佳计划。

  4. 缓存:在查询优化计划中选择的最佳计划存储在缓存中,以便每当下一个 当相同的查询进入时,它不必再次经历第 1 阶段、第 2 阶段和第 3 阶段。 下次查询进来时,将直接在缓存中检查并从那里获取 来执行。

  5. 执行阶段:在此阶段,将执行提供的查询,并将数据作为对象返回给用户。ResultSet

PreparedStatement API 在上述步骤中的行为

  1. PreparedStatements 不是完整的 SQL 查询,并且包含占位符, 在运行时,这些数据将替换为用户提供的实际数据。

  2. 每当将任何包含占位符的 PreparedStatment 传递到 SQL Server 引擎时, 它经过以下阶段

    1. 解析和归一化阶段
    2. 编译阶段
    3. 查询优化计划
    4. 缓存(带有占位符的已编译查询存储在缓存中。

UPDATE 用户设置用户名=?和 password=?其中 id=?

  1. 上面的查询会被解析,用占位符编译作为特殊处理,优化和 get 缓存。 此阶段的查询已编译并转换为机器可理解的格式。 因此,我们可以说存储在缓存中的查询是预编译的,并且 只需将占位符替换为用户提供的数据。

  2. 现在,在运行时,当用户提供的数据传入时,将从缓存中选取预编译查询,并将占位符替换为用户提供的数据。

PrepareStatementWorking

(请记住,在占位符替换为用户数据后,最终查询不是 再次编译/解释,SQL Server 引擎将用户数据视为纯数据,而不是 需要重新解析或编译的 SQL;这就是 PreparedStatement 的美妙之处。

如果查询不必再次经历编译阶段,则无论替换什么数据 占位符被视为纯数据,对 SQL Server 引擎和它没有直接意义 执行查询。

注意:解析阶段之后的编译阶段,理解/解释查询 结构并为其提供有意义的行为。对于 PreparedStatement,查询是 只编译一次,缓存的编译查询一直被拾取替换 用户数据并执行。

由于 PreparedStatement 的一次性编译特性,因此无需 SQL 注入 攻击。

您可以在此处通过示例获得详细说明:https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html

评论

7赞 Dheeraj Joshi 3/19/2018
很好的解释
12赞 jouell 3/28/2018
从字面上看,这是关于它如何工作的最完整的答案
2赞 Unknown 12/14/2019
这很有帮助。感谢您的详细解释。
1赞 Vlad Mihalcea 11/8/2016 #8

如果您仍在连接字符串,则仅 PreparedStatement 对您没有帮助。

例如,一个流氓攻击者仍然可以执行以下操作:

  • 调用 sleep 函数,以便所有数据库连接都处于繁忙状态,从而使应用程序不可用
  • 从数据库中提取敏感数据
  • 绕过用户身份验证

如果不使用 bind 参数,不仅 SQL,甚至 JPQL 或 HQL 也会受到损害。

总而言之,在构建 SQL 语句时,永远不要使用字符串连接。为此,请使用专用 API,例如 JPA Criteria API。

评论

1赞 Wild Pottok 10/18/2018
感谢您指出使用参数绑定的重要性,而不是单独使用 PreparedStatement。但是,您的回复似乎暗示使用专用 API 对于防止 SQL 注入是必要的。既然情况并非如此,并且使用带有参数绑定的 PreparedStatement 也有效,您是否愿意重新表述?
3赞 jack 1/10/2018 #9

SQL注入:当用户有机会输入可能成为SQL语句一部分的内容时

例如:

字符串查询 = “INSERT INTO students VALUES('” + user + “')”

当用户输入“Robert”时);DROP TABLE 学生;–“作为输入,它会导致 SQL 注入

准备好的声明如何防止这种情况发生?

字符串查询 = “INSERT INTO students VALUES('” + “:name” + “')”

parameters.addValue(“名称”, 用户);

=> 当用户再次输入“Robert”时);DROP TABLE 学生;–“,输入字符串在驱动程序上预编译为文字值,我想它可以像这样转换:

演员('罗伯特');DROP TABLE 学生;–' AS varchar(30))

因此,在最后,字符串将作为名称插入到表中。

http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/

评论

1赞 Héctor Álvarez 7/24/2018
如果我没记错的话,来自的部分会损坏,如果是这样的话,它将继续删除表格。它确实停止了注入,所以我相信这个例子还不够完整,无法解释这个场景。CAST(‘Robert’);CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))
-3赞 Shreyas K 7/17/2020 #10

在预准备语句中,用户被迫输入数据作为参数。如果用户输入一些易受攻击的语句,如 DROP TABLE 或 SELECT * FROM USERS,则数据不会受到影响,因为这些语句将被视为 SQL 语句的参数

评论

0赞 Julien Maret 7/17/2020
与所选答案相同的答案,但精度较低。