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


122

我知道PreparedStatements可以避免/防止SQL注入。它是如何做到的?使用PreparedStatements构造的最终表单查询是否为字符串?


3
从技术上讲,JDBC规范并不坚持没有SQL注入漏洞。我不知道任何受影响的驱动器。
Tom Hawtin-大头钉

3
@Jayesh我建议在此处添加您的博客内容作为答案。大多数答案只是告诉您动态SQL查询生成和准备好的stmt之间的差异。他们没有解决为什么准备好的语句能像您的博客那样更好地工作的问题。
帕万·曼朱纳斯

1
作为答案添加,希望对您有所帮助。
Jayesh 2015年

Answers:


78

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

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


1
可以,但是您仍然可以对部分或全部参数进行硬编码。
tangens

16
请使用示例-但是,如果您不使用用户输入作为已准备好的语句的参数,而是通过将字符串连接在一起来构建SQL命令,那么即使使用已准备好的语句,您仍然容易受到SQL注入的攻击。
david blaine

4
FWIW预处理语句不是JDBC,而是SQL。您可以从SQL控制台中准备和执行准备好的语句。PreparedStatement仅在JDBC中支持它们。
beldaz

198

考虑两种做同一件事的方法:

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();

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

Robert'); DROP TABLE students; --

然后,首先,您将被迫接管。第二,为了安全起见,Little Bobby Tables将为您的学校注册。


8
因此,如果我做对了,在第二个示例中将要执行的查询实际上是:INSERT INTO student VALUES(“ Robert'); DROP TABLE students;-”)-或至少是类似的东西。这是真的?
最多

18
不,在第一个实例中,您会得到该语句。在第二个中,它将在用户表中插入“ Robert'); DROP TABLE students;-”。
Paul Tomblin

3
那就是我的意思,在第二个示例(“安全”示例)中,字符串Robert'); DROP TABLE学生;-将保存到学生表的字段中。我还写其他东西吗?;)
最多

7
抱歉,由于这样的混乱,我想避免嵌套引号。这就是为什么我喜欢带有参数的PreparedStatements的原因。
Paul Tomblin,2009年

59
小鲍比表。XD大参考
Amalgovinus

128

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

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

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

查询执行阶段

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

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

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

  4. 高速缓存: 在查询优化计划中选择的最佳计划存储在高速缓存中,这样,每当下一次出现相同查询时,就不必再次通过阶段1,阶段2和阶段3。下次查询到来时,将直接在Cache中对其进行检查并从那里进行查询。

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

上述步骤中PreparedStatement API的行为

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

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

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

UPDATE用户集username =?和密码=?id =?

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

  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


3
很好的解释
Dheeraj Joshi

4
从字面上看,它是如何完成工作的最完整答案
jouell

这很有帮助。感谢您的详细解释。
未知

26

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


它不一定要那样实现,我相信通常不是那样。
汤姆·霍顿

4
实际上,SQL通常是在数据库上预编译的。即,在数据库上准备了执行计划。执行查询时,将使用这些参数执行计划。额外的好处是,相同的语句可以用不同的参数执行,而查询处理器不必每次都编译新计划。
beldaz

3

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

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

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

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

现在,尝试传递一个字符串变量(其内容不可接受作为数字参数)。走着瞧吧?


3

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

例如stmt.setString(1, user);user参数转换为字符串。

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

它将添加元字符(也称为自动转换)。

这使其更安全。


2

SQL注入:当用户有机会输入可能属于sql语句的内容时

例如:

字符串查询=“将学生插入VALUES('” +用户+“')”

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

准备好的语句如何防止这种情况?

字符串查询=“向学生插入VALUES('” +“:name” +“')”

parameters.addValue(“ name”,用户);

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

CAST('Robert'); DROP TABLE学生;–'AS varchar(30))

因此,最后,该字符串将作为表的名称逐字插入。

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


1
如果我没有记错的话,CAST(‘Robert’);from的那部分CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))会中断,那么如果是这样的话,它将继续删除该表。它确实停止了注入,因此我认为该示例还不够完整,无法解释该情况。
埃克托·阿尔瓦雷斯

1

PreparedStatement:

1)SQL语句的预编译和数据库侧缓存可提高整体执行速度,并能够批量重用同一SQL语句。

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


1

正如在解释这个帖子中,PreparedStatement本身并不能帮助你,如果你仍然在连接字符串。

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

  • 调用睡眠函数,以便您的所有数据库连接都将繁忙,因此使您的应用程序不可用
  • 从数据库中提取敏感数据
  • 绕过用户认证

如果不使用绑定参数,则不仅会损害SQL,甚至会损害JPQL或HQL。

最重要的是,在构建SQL语句时,绝对不要使用字符串连接。为此使用专用的API:


1
感谢您指出使用参数绑定而不是仅使用PreparedStatement的重要性。但是,您的答复似乎暗示必须使用专用的API来防止SQL注入。既然不是这种情况,并且将PreparedStatement与参数绑定一起使用也可以,那么您是否愿意重新编写?
Wild Pottok

-3

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


与所选答案相同的答案,精度较低。
Julien Maret
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.