关于安全性:预准备语句如何防止SQL注入攻击?

How can prepared statements protect from SQL injection attacks?

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

维基百科说:

Prepared statements are resilient against SQL injection, because
parameter values, which are transmitted later using a different
protocol, need not be correctly escaped. If the original statement
template is not derived from external input, SQL injection cannot
occur.

我看不清楚原因。 简单的英语和一些例子中有什么简单的解释?


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

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

实际上,我们的SQL查询是一个合法的程序。
我们通过动态添加一些数据来动态创建这样的程序。因此,这些数据可能会干扰程序代码甚至改变它,因为每个SQL注入示例都会显示它(PHP / Mysql中的所有示例):

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

将产生一个常规查询

1
SELECT * FROM users WHERE id=1

而这段代码

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

会产生恶意序列

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

它的工作原理是因为我们将数据直接添加到程序体中并且它成为程序的一部分,因此数据可能会改变程序,并且根据传递的数据,我们将有一个常规输出或表users已删除。

虽然在准备好的陈述中我们不会改变我们的计划,但它仍然完好无损
这才是重点。

我们首先将程序发送到服务器

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

其中数据被称为参数或占位符的变量替换。

请注意,发送到服务器的查询完全相同,没有任何数据!然后我们用第二个请求发送数据,基本上与查询本身分开:

1
$db->EXECUTE($data);

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

但是,值得注意的是,并非每次使用占位符时,都会将其作为预处理语句处理。

占位符是将实际数据替换为变量以供将来处理的一般想法(例如,参见printf()),而预准备语句是它的唯一子集。

有些情况(特别是PHP中的PDO可以这样做)可以模拟准备好的语句,并且查询实际上与数据一起组成并在一个请求中发送到服务器。但重要的是要理解这种方法同样安全,因为每一位数据都根据其类型进行了适当的格式化,因此不会发生任何错误。

我必须添加的唯一内容总是在每个手册中省略:

准备好的语句只能保护数据,但不能保护程序本身。
因此,一旦我们必须添加一个动态标识符 - 例如字段名称,准备好的语句就无法帮助我们。我最近解释了这件事,所以我不再重复了。


这是用于设置示例的SQL:

1
2
3
4
5
6
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的信息。基于用户输入的工资或奖金。但恶意用户操纵输入会破坏查询,方法是将相应的'或true'添加到where子句,以便返回所有内容,包括有关应该隐藏的Aaron的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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"));
        }
    }
}

运行此操作,第一种情况是正常使用,第二种情况是恶意注入:

1
2
3
4
5
6
7
8
9
10
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语句缓存命中,而绑定示例始终运行相同的语句)。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

1
2
3
4
5
6
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查询以找到有效的查询计划,并发送可能在以后从表单中获取的实际值 - 此时查询实际执行。

更多信息在这里:

准备语句和SQL注入


我仔细阅读了答案,仍然觉得有必要强调说明准备陈述本质的关键点。考虑两种方法来查询涉及用户输入的数据库:

天真的方法

一个用户输入与一些部分SQL字符串连接以生成SQL语句。在这种情况下,用户可以嵌入恶意SQL命令,然后将其发送到数据库以供执行。

1
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"

简而言之,使用预先准备好的语句将不会执行通过用户输入引入的恶意代码!


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

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

这意味着使用预准备语句执行SQL注入根本不可能。准备好的语句的本质及其与DBMS的关系可以防止这种情况。


在SQL  Server中,使用预准备语句肯定是防注入的,因为输入参数不构成查询。这意味着执行的查询不是动态查询。
SQL注入易受攻击语句的示例。

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

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

1
SELECT * FROM TABLE WHERE username='a' OR 1=1 -- and password=asda

其余的在--之后进行了注释,因此它永远不会被执行和绕过,如下所示使用准备好的语句示例。

1
2
3
4
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。这意味着你不要担心人们试图抛出破折号,撇号,引号等等......

这一切都是为您处理的。


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

让我们假设您在Servlet中拥有它。如果一个恶意的人为"过滤器"传递了错误的值,那么你可能会破解你的数据库。


根本原因#1 - 定界问题

Sql注入是可能的,因为我们使用引号来分隔字符串并且也是字符串的一部分,因此有时无法解释它们。如果我们有不能在字符串数据中使用的分隔符,那么sql注入永远不会发生。解决分隔符问题消除了sql注入问题。结构查询就是这样。

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

sql注入的另一个根本原因是人性。人们,包括程序员,都会犯错误。当您在结构化查询中出错时,它不会使您的系统容易受到SQL注入攻击。如果您不使用结构化查询,则错误可能会生成SQL注入漏洞。

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

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

结构化查询有助于防止人为错误造成严重的安全漏洞。
关于人类犯错误,使用结构查询时不会发生sql注入。有一些方法可以防止sql注入不涉及结构化查询,但是这种方法中的正常人为错误通常会导致至少一些sql注入。结构化查询从sql注入失败安全。你可以使用结构化查询,与任何其他编程一样,在世界上犯下所有错误,但是你可以做的任何事都可以变成sql注入接管的ssstem。这就是为什么人们喜欢说这是防止sql注入的正确方法。

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