PreparedStatement如何避免或防止SQL注入?
我知道PreparedStatements避免/阻止SQL注入。 它是如何做到的? 使用PreparedStatements构造的最终forms查询将是一个string还是其他?
SQL注入的问题在于,用户input被用作SQL语句的一部分。 通过使用预处理语句,可以强制将用户input作为参数的内容进行处理(而不是作为SQL命令的一部分)。
但是,如果不使用用户input作为准备语句的参数,而是通过将string连接在一起来构buildSQL命令,即使使用预准备语句, 仍然很容易受到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();
如果“用户”来自用户input并且用户input是
Robert'); DROP TABLE students; --
那么首先,你会被冲洗。 第二,你会安全的,小鲍比表将被注册为你的学校。
为了理解PreparedStatement如何防止SQL注入,我们需要了解SQL Query执行的各个阶段。
1.编译阶段。 2.执行阶段。
每当SQL服务器引擎收到一个查询,它必须经过下面的阶段,
-
parsing和规范化阶段:在这个阶段,检查查询的语法和语义。 它检查查询中使用的引用表和列是否存在。 它还有许多其他的工作要做,但是我们不要详细讨论。
-
编译阶段:在这个阶段,查询中使用的关键字如select,from,where等被转换成机器可以理解的格式。 这是查询被解释的阶段,并且决定相应的行动。 它还有许多其他的工作要做,但是我们不要详细讨论。
-
查询优化计划:在此阶段,创build决策树用于查找可以执行查询的方式。 它找出可以执行查询的方式的数量以及与执行查询的每种方式相关的成本。 它select执行查询的最佳计划。
-
caching:在查询优化计划中select的最佳计划存储在caching中,以便每当下一次相同的查询进入时,不必再次通过阶段1,阶段2和阶段3。 当下一次查询进入时,将直接在Cache中检查并从那里接收执行。
-
执行阶段:在这个阶段,提供的查询被执行,数据作为
ResultSet
对象返回给用户。
PreparedStatement API在上述步骤中的行为
-
PreparedStatements不是完整的SQL查询,并且包含占位符,它们在运行时被实际的用户提供的数据取代。
-
每当包含占位符的PreparedStatment被传递给SQL Server引擎时,它就会经过下面的阶段
- parsing和规范化阶段
- 编译阶段
- 查询优化计划
- caching(带有占位符的编译查询存储在caching中。)
更新用户设置用户名=? 和密码=? WHERE id =?
-
上面的查询将被parsing,用占位符编译为特殊处理,优化并获取caching。 在这个阶段的查询已经被编译并以机器可理解的格式转换。 所以我们可以说存储在caching中的查询是预编译的,只有占位符需要用用户提供的数据replace。
-
现在,在用户提供的数据进入运行时,预编译的查询将从caching中提取,占位符将被用户提供的数据replace。
(请记住,用户数据replace占位符后,最终查询不会再次编译/解释,SQL Server引擎将用户数据视为纯数据,而不是需要再次parsing或编译的SQL;这就是PreparedStatement的美妙之处。 )
如果查询不必再次经历编译阶段,那么在占位符上replace的任何数据都被视为纯数据,并且对SQL Server引擎没有任何意义,并直接执行查询。
注意:这是parsing阶段后的编译阶段,它理解/解释查询结构并给出有意义的行为。 在PreparedStatement的情况下,查询只编译一次,caching的编译查询一直被取出,以replace用户数据并执行。
由于PreparedStatement的一次编译function,它没有SQL注入攻击。
你可以在这里用例子得到详细的解释: http : //javabypatel.blogspot.in/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html
PreparedStatement中使用的SQL在驱动程序上预编译。 从那时开始,这些参数作为文字值发送到驱动程序,而不是SQL的可执行部分; 因此没有SQL可以使用参数注入。 PreparedStatements(预编译+只发送参数)的另一个有益的副作用是在多次运行语句(即使参数的值不同)(假定驱动程序支持PreparedStatements)时驱动程序不必每次都执行SQLparsing和编译时间参数改变。
我想这将是一个string。 但是input参数将被发送到数据库,并且会在创build实际的SQL语句之前应用适当的转换/转换。
举个例子,它可能会尝试查看CAST / Conversion是否有效。
如果有效,它可以创build一个最终的声明。
SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))
尝试使用接受数字参数的SQL语句的示例。
现在,尝试传递一个stringvariables(使用数字参数可接受的数字内容)。 它会引发任何错误吗?
现在,尝试传递一个stringvariables(以不可接受的内容作为数字参数)。 走着瞧吧?
准备好的声明更安全。 它会将参数转换为指定的types。
例如stmt.setString(1, user);
将user
参数转换为一个string。
假设参数包含一个包含可执行命令的SQLstring :使用预处理语句将不允许这样做。
它增加了元字符(又名自动转换)。
这使得它更安全。
正如在这篇文章中所解释的那样, PreparedStatement
本身并不能帮助你,如果你还在串联string。
例如,一个stream氓攻击者仍然可以做到以下几点:
- 调用一个睡眠函数,以便所有的数据库连接都会忙,因此使您的应用程序不可用
- 从数据库中提取敏感数据
- 绕过用户authentication
如果不使用绑定参数,不仅SQL,甚至JPQL或HQL都可能被破坏。
底线,在构buildSQL语句时,不应该使用string连接。 为此目的使用专用的API:
- JPA标准API
- jOOQ
PreparedStatement的:
1)SQL语句的预编译和DB端caching导致总体上更快的执行以及批量重用相同SQL语句的能力。
2)通过内置的引号转义和其他特殊字符自动防止SQL注入攻击。 请注意,这要求您使用任何PreparedStatement setXxx()方法来设置值。