繁体   English   中英

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

[英]How does a PreparedStatement avoid or prevent SQL injection?

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

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

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 会在你的学校注册。

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

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

每当 SQL 服务器引擎收到查询时,它必须通过以下阶段,

查询执行阶段

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

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

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

  4. 缓存:查询优化计划中选择的最佳计划存储在缓存中,以便下次相同的查询进入时,不必再次通过阶段1、阶段2和阶段3。 下次查询进来时,会直接在Cache中进行检查,然后从那里取出执行。

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

PreparedStatement API 在上述步骤中的行为

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

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

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

更新用户集用户名=? 和密码=? 哪里 id=?

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

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

准备语句工作

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

如果查询不必再次经过编译阶段,那么占位符上替换的任何数据都被视为纯数据,对 SQL Server 引擎没有意义,它直接执行查询。

注意:解析阶段之后的编译阶段,理解/解释查询结构并为其提供有意义的行为。 在 PreparedStatement 的情况下,查询只编译一次,缓存的编译查询一直被选中以替换用户数据并执行。

由于 PreparedStatement 的一次性编译特性,它不受 SQL 注入攻击。

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

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

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

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

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

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

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

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

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

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

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

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

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

这使得它更安全。

SQL 注入:当用户有机会输入一些可能是 sql 语句的一部分时

例如:

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

当用户输入“罗伯特”); DROP TABLE 学生; –”作为输入,导致SQL注入

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

String query = “INSERT INTO Students VALUES('” + “:name” + “')”

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

=> 当用户再次输入“罗伯特”); DROP TABLE 学生; –“,输入字符串在驱动程序上预编译为文字值,我想它可能会被转换为:

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

所以最后,字符串将作为名称插入到表中。

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

准备语句:

1) SQL 语句的预编译和 DB 端缓存导致整体执行速度更快,并且可以批量重用相同的 SQL 语句。

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

如果您仍在连接字符串,单独的PreparedStatement对您没有帮助。

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

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

如果您不使用绑定参数,不仅 SQL,甚至 JPQL 或 HQL 都可能受到损害。

最重要的是,在构建 SQL 语句时,永远不要使用字符串连接。 为此目的使用专用 API,例如 JPA Criteria API。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM