准备好的语句如何防止SQL注入攻击?

准备好的语句如何帮助我们防止SQL注入攻击?

维基百科说:

准备好的语句对SQL注入是有弹性的,因为稍后使用不同的协议传输的参数值不需要被正确地转义。 如果原始语句模板不是从外部input派生的,则不会发生SQL注入。

我看不出原因。 简单的英语和一些例子,简单的解释是什么?

这个想法很简单 – 查询和数据分别发送到SQL服务器。
就这样。

SQL注入问题的根源是代码和数据的混合。

实际上,我们的SQL查询是一个完整的合法程序 。 而且我们正在dynamic地添加一些数据来创build这个程序。 因此,这些数据可能会干扰程序代码甚至修改程序代码 ,因为每个注入示例都会显示它:

$expected_data = 1; $query = "SELECT * FROM users where id=$expected_data"; 

会产生一个定期的查询

 SELECT * FROM users where id=1 

而这个代码

 $spoiled_data = "1; DROP TABLE users;" $query = "SELECT * FROM users where id=$spoiled_data"; 

会产生恶意序列

 SELECT * FROM users where id=1; DROP TABLE users; 

这是因为我们直接将数据添加到程序主体,并成为程序的一部分。

所以,数据可能会改变程序,并根据传递的数据,我们将有正常输出或表users删除。

在准备好陈述的情况下,我们不会改变我们的计划,它仍然是完整的
这才是重点。

我们正在将程序发送到服务器

 $db->prepare("SELECT * FROM users where id=?"); 

数据被一些名为“占位符”的variables替代。

请注意,相同的查询被发送到服务器,没有任何数据! 然后我们发送第二个请求的数据,完全从查询本身分离出来:

 $db->execute($data); 

所以,它不能改变我们的计划,并造成任何伤害。
很简单 – 不是吗?

但值得一提的是, 并不是每次使用占位符时,都会将其作为准备好的语句进行处理

占位符是将实际数据replace为将来处理的variables的一般想法,而准备好的语句是它们的唯一子集。

所以,有时候我们实际上不得不随数据一起编写查询,但是如果每一个数据都按照它的types正确地格式化 – 没有什么不对的

因此,例如,默认情况下,PDO 不使用预处理语句 ,而是组成常规查询。 但是因为它将每个占位符replace为格式正确的数据,所以没有什么不好的事情发生。

但是你要告诉PDO直接做,使用真实的准备语句,你必须设置这个设置:

 $dbh->setAttribute( PDO::ATTR_EMULATE_PREPARES, false ); 

另请注意, 格式不要与转义混淆 。 格式化涉及更多的行动,而不是愚蠢的逃避。 所以,不要在家里尝试手动转义,至less要知道每个不同查询部分的每个格式化规则。

我必须添加的唯一东西,在每个手册中总是省略:

准备好的语句只能保护数据 ,但不能保护程序本身
所以,一旦我们必须添加一个dynamic的标识符 – 一个字段名称,例如,准备好的语句不能帮助我们。 我最近解释了这个问题 ,所以我不会再重复一遍。

这里是SQLbuild立一个例子:

 CREATE TABLE employee(name varchar, paymentType varchar, amount bigint); INSERT INTO employee VALUES('Aaron', 'salary', 100); INSERT INTO employee VALUES('Aaron', 'bonus', 50); INSERT INTO employee VALUES('Bob', 'salary', 50); INSERT INTO employee VALUES('Bob', 'bonus', 0); 

Inject类容易受到SQL注入的影响。 查询dynamic地与用户input一起粘贴。 查询的目的是显示有关Bob的信息。 无论是工资还是奖金,都是基于用户的input。 但恶意用户操纵input,通过在where子句上加上“或true”的等价物来破坏查询,以便返回所有内容,包括应该隐藏的关于Aaron的信息。

 import java.sql.*; public class Inject { public static void main(String[] args) throws SQLException { String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd"; Connection conn = DriverManager.getConnection(url); Statement stmt = conn.createStatement(); String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'"; System.out.println(sql); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount")); } } } 

运行这个,第一种情况是正常使用,第二种情况是恶意注入:

 c:\temp>java Inject salary SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' salary 50 c:\temp>java Inject "salary' OR 'a'!='b" SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b' salary 100 bonus 50 salary 50 bonus 0 

您不应该使用用户input的string连接来构build您的SQL语句。 它不仅容易受到注入的影响,而且在服务器上也有caching的含义(语句发生变化,因此获取SQL语句高速caching命中的可能性较小,而绑定示例总是运行相同的语句)。

这里是一个绑定的例子,以避免这种注入:

 import java.sql.*; public class Bind { public static void main(String[] args) throws SQLException { String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres"; Connection conn = DriverManager.getConnection(url); String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?"; System.out.println(sql); PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, args[0]); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount")); } } } 

使用与前面示例相同的input运行此代码显示恶意代码不起作用,因为没有与该string匹配的paymentType:

 c:\temp>java Bind salary SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=? salary 50 c:\temp>java Bind "salary' OR 'a'!='b" SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=? 

基本上,准备好的语句将来自潜在黑客的数据视为数据,而且无法将其与应用程序SQL混合和/或解释为SQL(数据传入时直接放入您的数据应用程序SQL)。

这是因为准备好的语句首先“准备”SQL查询以find一个有效的查询计划,然后发送实际的值,这些值可能来自一个表单,那个时候查询实际上是执行的。

这里有更多的信息:

编写语句和SQL注入

在SQL Server中 ,使用准备好的语句绝对是防注入的,因为input参数不构成查询。 这意味着执行的查询不是一个dynamic查询。 SQL注入易受攻击语句的示例。

 string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'"; 

现在,如果inoutusernamevariables中的值类似于'或1 = 1',则此查询现在变为:

 select * from table where username='a' or 1=1 -- and password=asda 

剩下的就是--之后的评论,所以它不会被执行和绕过使用准备好的语句示例如下。

 Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass"); command.Parameters.Add(new SqlParameter("@userinput", 100)); command.Parameters.Add(new SqlParameter("@pass", 100)); command.prepare(); 

所以实际上,你不能发送另一个参数,从而避免SQL注入…

关键短语是need not be correctly escaped 。 这意味着你不必担心人们试图扔破折号,撇号,报价等…

这一切都为你处理。

 ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter"); 

假设你在Servlet中有这个权限。 如果一个恶意的人通过了一个错误的“filter”值,你可能会破解你的数据库。

当您创build准备好的语句并将其发送到DBMS时,它将存储为SQL查询以供执行。

稍后,将数据绑定到查询,以便DBMS将该数据用作执行(参数化)的查询参数。 DBMS不使用您绑定的数据作为已编译的SQL查询的补充; 这只是数据。

这意味着使用预处理语句执行SQL注入是根本不可能的。 准备好的陈述的本质以及与DBMS的关系阻止了这一点。

根本原因#1 – 分界问题

SQL注入是可能的,因为我们使用引号来分隔string,也是string的一部分,有时不可能解释它们。 如果我们有string数据中不能使用的分隔符,sql注入将不会发生。 解决分隔问题消除了SQL注入问题。 结构查询这样做。

根本原因#2 – 人性,人狡猾, 有些狡猾的人是恶意的 ,所有人都犯了错误

sql注入的另一个根本原因是人性化的。 包括程序员在内的人会犯错。 当你在一个结构化查询上犯了一个错误,它不会让你的系统容易受到sql注入的攻击。 如果你不使用结构化查询,错误可能会产生sql注入漏洞。

结构化查询如何解决SQL注入的根本原因

结构化查询通过将sql命令放在一个语句中并将数据放在单独的编程语句中来解决分隔符问题。 编程语句创build所需的分离。

结构化查询有助于防止人为错误造成严重的安全漏洞。 对于犯错误的人来说,使用结构查询时不会发生sql注入。 有一些方法可以防止不涉及结构化查询的sql注入,但是这种方法中的正常人为错误通常会导致sql注入的一些暴露。 结构化查询是从sql注入失败。 你几乎可以用世界上所有的错误来进行结构化查询,就像任何其他的编程一样,但是你可以把所有的错误都变成一个被sql注入的系统。 这就是为什么人们喜欢说这是防止SQL注入的正确方法。

所以,你有它,SQL注入的原因和自然结构的查询,使他们不可能使用时。