准备好的语句如何帮助我们防止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”在上面的例子中永远不会被执行。

简而言之,通过用户输入引入的恶意代码将不会被执行!

其他回答

在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注入示例所显示的那样(所有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);

所以它不会改变我们的程序,造成任何伤害。 很简单,不是吗?

我唯一需要补充的是,每个手册中都省略了:

预准备语句只能保护数据文字,但不能与任何其他查询部分一起使用。 因此,一旦我们必须添加一个动态标识符——例如,一个字段名——预处理语句就无法帮助我们。这件事我最近已经解释过了,我就不再重复了。

简单的例子:

  "select * from myTable where name = " + condition;

如果用户输入是:

  '123'; delete from myTable; commit;

查询将像这样执行:

  select * from myTable where name = '123'; delete from myTable; commit;

基本上,通过准备好的语句,来自潜在黑客的数据被视为数据——它不可能与应用程序SQL混合和/或被解释为SQL(当传入的数据直接放入应用程序SQL时可能发生)。

这是因为预处理语句首先“准备”SQL查询,以找到一个有效的查询计划,然后发送可能来自表单的实际值——在查询实际执行的时候。

更多信息请点击这里:

准备好的语句和SQL注入

下面是一个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=?