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


172

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

维基百科说:

准备好的语句可以抵御SQL注入,因为稍后需要使用其他协议传输的参数值不需要正确地转义。如果原始语句模板不是从外部输入派生的,则不会发生SQL注入。

我不太清楚原因。用简单的英语和一些例子,简单的解释是什么?

Answers:


290

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

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;

之所以起作用,是因为我们将数据直接添加到程序主体中,并且它成为程序的一部分,因此数据可能会更改程序,并且根据传递的数据,我们将获得常规输出或表 users删除。

虽然在准备语句的情况下我们不会更改程序,但程序仍然完整

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

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

数据被某些变量替代的地方称为参数或占位符的。

请注意,完全相同的查询将发送到服务器,其中没有任何数据!然后,我们通过第二个请求发送数据,该请求实际上与查询本身是分开的:

$db->execute($data);

因此它不能更改我们的程序并造成任何伤害。
很简单-是吗?

我必须补充的唯一一件事是在每本手册中始终省略:

预处理语句只能保护数据文字,但不能与其他任何查询部分一起使用。
因此,一旦必须添加动态标识符(例如字段名),准备好的语句就无济于事。我最近已经解释了这个问题,所以我不再重复。


2
“例如,默认情况下,PDO不使用预处理语句”-并非完全正确,因为PDO仅针对不支持该功能的驱动程序模拟预处理语句。
pinepain

3
@ zaq178miami:“ PDO仅为不支持该功能的驱动程序模拟准备好的语句”-并非完全正确。MySQL支持准备好的语句已经有一段时间了。PDO驱动程序也有。但是,我上次检查时,默认情况下PDO仍会准备MySQL查询。
cHao

9
$spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";$db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> 相比有什么不同$db->execute($data);。他们会做同样的事情吗?
Juha Untinen 2014年

14
@Juha Untinen数据可以是任何数据。它不会解析数据。那是DATA而不是命令。因此,即使$ data包含sql命令,也不会执行它。同样,如果id是数字,则字符串内容将生成报告或零值。
索利2015年

21

这是用于设置示例的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子句上添加“或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语句缓存的可能性较小,而bind示例始终在运行同一语句)。

这是避免这种注入的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=?

使用连接到数据库的程序中的预处理语句是否与使用数据库中的预处理语句具有相同的效果?例如Postgres有它自己的准备好的语句,使用它会阻止SQL注入吗?postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas

@Celeritas我对Postgresql没有明确的答案。查看文档,看起来效果是一样的。PREPARE创建一个已解析的固定命名语句(即无论输入内容如何,​​该语句都将不再更改),同时EXECUTE运行绑定参数的命名语句。由于PREPARE只有会话持续时间,因此实际上看起来是出于性能原因,而不是为了防止通过psql脚本进行注入。对于psql访问,可以授予存储过程权限并在proc中绑定参数。
Glenn 2015年

@Celeritas我在x86_64上使用PostgreSQL 11.1和上面的SQLi示例尝试了上面的代码。
克里希纳·潘迪

15

基本上,使用准备好的语句,将来自潜在黑客的数据视为数据-并且无法将其与应用程序SQL混合和/或解释为SQL(当传入的数据直接放入您的应用程序中时可能会发生这种情况)应用程序SQL)。

这是因为准备好的语句首先“准备” SQL查询以找到有效的查询计划,然后发送大概是从表单中传入的实际值,这时实际上是在执行查询。

更多详细信息:

预备语句和SQL注入


6

我通读了答案,但仍然感到有必要强调关键点,以阐明预备陈述的本质。考虑两种查询涉及用户输入的数据库的方法:

天真的方法

一个将用户输入与部分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"在上述情况下将永远不会执行。

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


真?接受的答案不能准确说明这一点吗?
您的常识

@您的常识接受的答案包含很多有价值的信息,但它使我想知道数据和查询分离的实现细节是什么。而将注意力集中在永远不会执行恶意注入的数据(如果有的话)这一点上,真是令人震惊。
N.Vegeta

您的答案中提供了哪些“实现细节”,而此处没有出现?
您的常识'18

如果您尝试查看我的来历,您将意识到我的意思如下:希望了解实现细节的短暂愿望源于对理解恶意用户输入不会引起任何后果的明确原因的需要。危害。无需查看实现细节。这就是为什么意识到实现细节是如此之大,以至于在任何时候都不会执行恶意输入的SQL,而是将消息发送回家。您的回答回答了这个问题,如何(按要求)?但是我想其他人(像我一样)对为什么要做出简短的答复会感到满意?
N.Vegeta '18

认为这是一种丰富之处,可以阐明症结所在,而不是作为隐含的批评(cen悔地意识到谁是接受答复的作者)。
N.Vegeta '18

5

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

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

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


4

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注入...


3

关键是 need not be correctly escaped。这意味着您不必担心人们会试图用破折号,撇号,引号引起来……

一切都为您处理。


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

假设您在Servlet中拥有正确的位置。如果一个恶意的人为“过滤器”传递了一个错误的值,那么您可能会入侵您的数据库。


0

根本原因1-分隔符问题

Sql注入是可能的,因为我们使用引号来分隔字符串,并且也将其用作字符串的一部分,从而有时无法解释它们。如果我们有不能在字符串数据中使用的定界符,那么SQL注入将永远不会发生。解决定界符问题可以消除sql注入问题。结构查询可以做到这一点。

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

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

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

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

结构化查询有助于防止人为错误造成严重的安全漏洞。 对于人为的错误,使用结构查询时不会发生sql注入。有一些防止sql注入的方法不涉及结构化查询,但是这种方法中的正常人为错误通常至少会导致一定程度的sql注入。结构化查询从sql注入是故障安全的。与其他任何编程一样,几乎可以通过结构化查询来实现世界上所有的错误,但是您所不能犯的任何错误都可以变成sql注入所取代的系统。这就是为什么人们喜欢说这是防止sql注入的正确方法的原因。

因此,有了SQL注入的原因以及自然结构化的查询,这些查询使它们在使用时变得不可能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.