准备好的语句如何帮助我们防止SQL注入攻击?
维基百科说:
预处理语句对SQL注入具有弹性,因为 参数值,稍后使用不同的 协议,不需要正确转义。如果原始语句 模板不能从外部输入派生,SQL注入不能 发生。
我不太明白其中的原因。如何用简单的英语和一些例子来简单地解释?
准备好的语句如何帮助我们防止SQL注入攻击?
维基百科说:
预处理语句对SQL注入具有弹性,因为 参数值,稍后使用不同的 协议,不需要正确转义。如果原始语句 模板不能从外部输入派生,SQL注入不能 发生。
我不太明白其中的原因。如何用简单的英语和一些例子来简单地解释?
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");
让我们假设在Servlet中有这个。如果一个恶意的人传递了一个坏的值为'过滤器',你可能会黑你的数据库。
下面是一个SQL语句来建立一个例子:
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注入的攻击。查询与用户输入动态地粘贴在一起。查询的目的是显示关于Bob的信息。工资或奖金,根据用户输入。但是恶意用户操纵输入,在where子句后面附加一个“or 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
您不应该使用用户输入的字符串连接来构建SQL语句。它不仅容易受到注入的攻击,而且还会对服务器产生缓存影响(语句更改,因此SQL语句缓存命中的可能性较小,而绑定示例总是运行相同的语句)。
下面是一个避免这种注入的Binding的例子:
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"));
}
}
}
使用与前面示例相同的输入运行此代码,显示恶意代码不起作用,因为没有匹配该字符串的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注入示例所显示的那样(所有PHP/Mysql中的示例):
$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;
它可以工作,因为我们直接将数据添加到程序主体中,它成为程序的一部分,因此数据可能会改变程序,并且根据传递的数据,我们将有一个常规输出或用户删除的表。
而对于准备好的语句,我们不改变我们的程序,它保持不变 这就是重点。
我们首先向服务器发送一个程序
$db->prepare("SELECT * FROM users where id=?");
其中数据被一些称为参数或占位符的变量所取代。
注意,完全相同的查询被发送到服务器,其中没有任何数据!然后我们将数据与第二个请求一起发送,本质上与查询本身分离:
$db->execute($data);
所以它不会改变我们的程序,造成任何伤害。 很简单,不是吗?
我唯一需要补充的是,每个手册中都省略了:
预准备语句只能保护数据文字,但不能与任何其他查询部分一起使用。 因此,一旦我们必须添加一个动态标识符——例如,一个字段名——预处理语句就无法帮助我们。这件事我最近已经解释过了,我就不再重复了。
在SQL Server中,使用准备好的语句绝对是防注入的,因为输入参数不构成查询。这意味着执行的查询不是动态查询。 SQL注入易受攻击语句的示例。
string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";
现在,如果inoutusername变量的值是类似于a'或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注入…
基本上,通过准备好的语句,来自潜在黑客的数据被视为数据——它不可能与应用程序SQL混合和/或被解释为SQL(当传入的数据直接放入应用程序SQL时可能发生)。
这是因为预处理语句首先“准备”SQL查询,以找到一个有效的查询计划,然后发送可能来自表单的实际值——在查询实际执行的时候。
更多信息请点击这里:
准备好的语句和SQL注入
当您创建准备好的语句并将其发送到DBMS时,它将存储为SQL查询以供执行。
稍后将数据绑定到查询,以便DBMS使用该数据作为执行(参数化)的查询参数。DBMS不会使用你绑定的数据作为已经编译的SQL查询的补充;只是数据而已。
这意味着使用准备好的语句执行SQL注入从根本上是不可能的。预处理语句的本质及其与DBMS的关系阻止了这一点。
根本原因#1 -分隔符问题
Sql注入是可能的,因为我们使用引号来分隔字符串,也作为字符串的一部分,使得有时无法解释它们。如果我们有不能在字符串数据中使用的分隔符,sql注入就永远不会发生。解决分隔符问题可以消除sql注入问题。结构查询可以做到这一点。
根本原因#2 -人性,人们是狡猾的,一些狡猾的人是恶意的,所有的人都会犯错误
sql注入的另一个根本原因是人性。人,包括程序员,都会犯错误。当您在结构化查询中犯错误时,它不会使您的系统容易受到sql注入的攻击。如果不使用结构化查询,错误可能会产生sql注入漏洞。
结构化查询如何解决SQL注入的根本原因
结构化查询解决了分隔符问题,方法是将sql命令放在一个语句中,将数据放在一个单独的编程语句中。编程语句创建所需的分离。
Structured queries help prevent human error from creating critical security holes. With regard to humans making mistakes, sql injection cannot happen when structure queries are used. There are ways of preventing sql injection that don't involve structured queries, but normal human error in that approaches usually leads to at least some exposure to sql injection. Structured Queries are fail safe from sql injection. You can make all the mistakes in the world, almost, with structured queries, same as any other programming, but none that you can make can be turned into a ssstem taken over by sql injection. That is why people like to say this is the right way to prevent sql injection.
所以,你已经了解了sql注入的原因,以及在使用sql注入时不可能使用sql注入的本质结构化查询。
我把答案通读了一遍,仍然觉得有必要强调阐明事先陈述的本质的关键点。考虑两种查询数据库的方法,其中涉及用户输入:
天真的方法
其中一个将用户输入与部分SQL字符串连接起来以生成SQL语句。在这种情况下,用户可以嵌入恶意SQL命令,然后将这些命令发送到数据库执行。
String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"
例如,恶意用户输入可能导致SQLString等于"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;' "
由于恶意用户的存在,SQLString包含2条语句,其中第二条语句(“DROP TABLE CUSTOMERS”)将造成损害。
准备好的语句
在这种情况下,由于查询和数据分离,用户输入永远不会被视为SQL语句,因此永远不会被执行。正是由于这个原因,注入的任何恶意SQL代码都不会造成损害。所以“DROP TABLE CUSTOMERS”在上面的例子中永远不会被执行。
简而言之,通过用户输入引入的恶意代码将不会被执行!
简单的例子:
"select * from myTable where name = " + condition;
如果用户输入是:
'123'; delete from myTable; commit;
查询将像这样执行:
select * from myTable where name = '123'; delete from myTable; commit;