我知道使用 PreparedStatements 可以避免 SQL 注入攻击。它是如何做到的呢?最终使用 PreparedStatements 构建的查询语句会是字符串形式还是其他形式?
我知道使用 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();
如果“user”来自用户输入,并且用户输入是Robert'); DROP TABLE students; --
在第一种情况下,你将会遭受损失。而在第二种情况下,你将会安全,并且“小博比·特别表”(Little Bobby Tables)会被注册到你的学校。
解析和规范化阶段: 在这个阶段,查询语句会被检查语法和语义。它会检查查询中引用的表格和列是否存在。 还有很多其他任务需要完成,但我们不详细讨论。
编译阶段: 在这个阶段,查询中使用的关键字(如select、from、where等)会被转换成机器可理解的格式。 这是解释查询并决定要采取的相应操作的阶段。还有很多其他任务需要完成,但我们不详细讨论。
查询优化计划: 在这个阶段,创建决策树来找到执行查询的方法。 它找出查询可以执行的方式数量以及每种执行查询方式的成本。 它选择最佳计划来执行查询。
缓存: 在查询优化计划中选择的最佳计划被存储在缓存中,以便下次遇到相同的查询时,不必再经过第1、2和3个阶段。 当下次查询进来时,将直接在缓存中进行检查并从中提取以执行。
执行阶段:
在这个阶段,提供的查询被执行并将数据作为ResultSet
对象返回给用户。
PreparedStatements不是完整的SQL查询,而是包含占位符,在运行时由实际提供的用户数据替换。
无论何时传递包含占位符的任何PreparedStatment给SQL Server引擎,它都会经过以下阶段:
UPDATE user set username=? and password=? WHERE id=?
上述查询将被解析、编译并进行特殊处理的占位符优化,并被缓存。 此时的查询已经被编译并转换成机器可理解的格式。 因此我们可以说存储在缓存中的查询是预编译的,只需要用用户提供的数据替换占位符。
现在,在运行时当用户提供的数据到来时,从缓存中挑选预编译的查询并用用户提供的数据替换占位符。
SQL注入问题在于用户输入被用作SQL语句的一部分。通过使用预编译语句,您可以强制将用户输入处理为参数的内容(而不是SQL命令的一部分)。
但如果您不使用用户输入作为预编译语句的参数,而是通过拼接字符串构建SQL命令,则即使使用预编译语句,您仍然会容易受到SQL注入攻击。
在PreparedStatement中使用的SQL语句是由驱动程序预编译的。从那时起,参数被作为字面值而不是可执行的SQL部分发送到驱动程序;因此,不能使用参数注入任何SQL。 PreparedStatement的另一个有益的副作用(预编译+仅发送参数)是,在多次运行语句时,即使参数的值不同(假设驱动程序支持PreparedStatement),也可以提高性能,因为驱动程序不必每次参数更改时执行SQL解析和编译。
我猜它会是一个字符串。但输入参数将被发送到数据库,并在创建实际的SQL语句之前应用适当的强制转换/转换。
举个例子,它可能会尝试并查看CAST / Conversion是否有效。
如果有效,它可以将其转化为最终语句。
SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))
尝试一个接受数字参数的SQL语句示例。
现在,尝试传递一个字符串变量(其内容可以作为数字参数接受)。它会引发任何错误吗?
现在,尝试传递一个字符串变量(其内容无法作为数字参数接受)。看看会发生什么?
SQL注入:当用户有机会输入可能成为SQL语句一部分的内容时
例如:
String query = “INSERT INTO students VALUES(‘” + user + “‘)”
当用户将“Robert’); DROP TABLE students; –”作为输入时,会导致SQL注入
准备好的语句如何防止这种情况发生?
String query = “INSERT INTO students VALUES(‘” + “:name” + “‘)”
parameters.addValue(“name”, user);
=> 当用户再次输入“Robert’); DROP TABLE students; –”时,输入字符串在驱动程序上被预编译为字面值,我想它可能会被转换为:
CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))
因此,最终字符串将被文字插入到表中。
http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/
CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))
中的部分 CAST(‘Robert’);
会出问题,然后如果是这种情况,它会继续删除表。它确实阻止了注入,因此我认为这个例子并不足以解释这种情况。 - Héctor Álvarez预编译语句更加安全。它会将参数转换为指定类型。
例如,stmt.setString(1, user);
将会把 user
参数转换成一个字符串。
假设参数包含一个 SQL 字符串并含有可执行命令:使用预编译语句将不允许这样做。
它会添加元字符(也称自动转换)到其中。
这使其更加安全。
PreparedStatement
单独使用并不能帮助您,如果仍然在连接字符串,则容易受到以下攻击:
不仅 SQL,即使是 JPQL 或 HQL 也可能会受到影响,如果您没有使用绑定参数。
总之,在构建 SQL 语句时,永远不要使用字符串连接。使用专用的 API,如 JPA Criteria API。
PreparedStatement:
1)预编译和数据库端缓存SQL语句可实现更快的执行速度,并能在批处理中重复使用同一SQL语句。
2)通过内置引号和其他特殊字符的转义,自动防止SQL注入攻击。请注意,这需要您使用任何PreparedStatement setXxx()方法来设置值。