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

维基百科说:

预处理语句对SQL注入具有弹性,因为 参数值,稍后使用不同的 协议,不需要正确转义。如果原始语句 模板不能从外部输入派生,SQL注入不能 发生。

我不太明白其中的原因。如何用简单的英语和一些例子来简单地解释?


当前回答

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

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

更多信息请点击这里:

准备好的语句和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);

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

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

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

关键短语是不需要正确转义。这意味着你不需要担心人们试图插入破折号、撇号、引号等等……

一切都为你安排好了。

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

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

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

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=?