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

我知道 PreparedStatement 可以避免/阻止 SQL 注入。它是怎么做到的?使用 PreparedStatments 构造的最终表单查询是否为字符串?

128443 次浏览

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

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

想想做同样事情的两种方法:

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

那么一开始,你就会被淘汰。第二,你就安全了“小鲍比桌子”会在你的学校注册。

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

为了给您一个示例,它可能会尝试查看 CAST/Conversion 是否有效。
如果成功了,它可以创建一个最终的声明。

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

尝试使用 SQL 语句接受数值参数的示例。
现在,尝试传递一个字符串变量(其中包含可以作为数值参数接受的数值内容)?

现在,尝试传递一个字符串变量(包含不能作为数值参数接受的内容)?

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

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

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

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

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

这样更安全。

准备声明:

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

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

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

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

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

Query Execution Phases

  1. 解析和规范化阶段: 在这个阶段,查询被检查语法和语义。它检查是否引用表和 查询中使用的列是否存在。 它还有许多其他任务要完成,但我们不要详细讨论

  2. 编制阶段: 在这个阶段,查询中使用的关键字如 select、 from、 where 等被转换成格式 机器所能理解的。 这是解释查询并确定要采取的相应操作的阶段。 它还有许多其他任务要完成,但我们不要详细讨论

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

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

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

PreparedStatement API 在上述步骤中的行为

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

  2. 每当将包含占位符的任何 PreparedStatement 传递到 SQLServer 引擎时, 它通过下面的阶段

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

UPDATE 用户集 username = ? 和 password = ? WHERE id = ?

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

  2. 现在,在运行时,当用户提供的数据进入时,从 Cache 拾取预编译查询,并用用户提供的数据替换占位符。

PrepareStatementWorking

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

如果查询不必再次经历编译阶段,那么在 占位符被视为纯数据,对 SQLServer 引擎和它直接没有意义 执行查询。

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

由于 PreparedStatement 具有一次性编译特性,因此不需要 SQL 注入 攻击。

你可以通过这里的例子得到详细的解释: Https://javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html

如果您仍然在连接字符串,仅凭 PreparedStatement 无法帮助您。

例如,一个流氓攻击者仍然可以做以下事情:

  • 调用一个 sleep 函数,这样您的所有数据库连接都将处于忙碌状态,从而使您的应用程序不可用
  • 从数据库中提取敏感数据
  • 绕过用户身份验证

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

底线是,在构建 SQL 语句时不应使用字符串串联。为此使用专用的 API,如 JPA Criteria API。

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

例如:

字符串查询 = “ INSERT INTO Student VALUES (‘”+ user + “’)”

当用户输入“ Robert”; DROPTABLE Student;-”作为输入时,它将导致 SQL 注入

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

字符串查询 = “ INSERT INTO Student VALUES (‘”+ “ : name”+ “’)”

AddValue (“ name”,user) ;

当用户再次输入“ Robert”时,输入字符串在驱动程序上被预先编译为文本值,我猜它可能是这样转换的:

CAST (‘ Robert’) ; 放下表格学生;-‘ AS varchar (30))

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

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

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