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

Aan*_*Aan 151 sql security sql-injection prepared-statement

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

维基百科说:

准备好的语句对SQL注入具有弹性,因为稍后使用不同协议传输的参数值无需正确转义.如果原始语句模板不是从外部输入派生的,则不能进行SQL注入.

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

You*_*nse 271

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

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

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

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";
Run Code Online (Sandbox Code Playgroud)

将产生一个常规查询

SELECT * FROM users where id=1
Run Code Online (Sandbox Code Playgroud)

而这段代码

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";
Run Code Online (Sandbox Code Playgroud)

会产生恶意序列

SELECT * FROM users where id=1; DROP TABLE users;
Run Code Online (Sandbox Code Playgroud)

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

虽然在准备好的陈述的情况下,我们不会改变我们的计划,但它仍然完好无损.
这就是重点.

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

$db->prepare("SELECT * FROM users where id=?");
Run Code Online (Sandbox Code Playgroud)

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

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

$db->execute($data);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

  • @JuhaUntinen我认为当使用准备好的语句执行查询时,查询将像这样执行 SELECT * FROM users where id='**1;DROP TABLE users**' ,所以这个值永远无法在数据库中找到,也不会返回任何东西。哦天哪!!所以它也让我们免于 SQL 注入!!!!!!干杯 (12认同)
  • @Juha Untinen数据可以是任何东西.它不会解析数据.那就是DATA而不是命令.因此,即使$ data包含sql命令,也不会执行它.此外,如果id是数字,则字符串内容将生成报告或值零. (11认同)
  • `$ spoiled_data ="1; DROP TABLE用户;"` - >`$ query ="SELECT*FROM users where id = $ spoiled_data";`,比较:`$ db-> prepare("SELECT*FROM用户id =?");` - >`$ data ="1; DROP TABLE用户;"` - >`$ db-> execute($ data);`.他们不会做同样的事情吗? (8认同)
  • @ zaq178miami:"PDO仅为不支持该功能的驱动程序模拟预处理语句" - 不完全正确.MySQL已经支持预备语句很长一段时间了.PDO驱动程序也有.但是,上次我检查时,默认情况下,PDO仍在准备MySQL查询. (3认同)
  • “例如,默认情况下PDO不使用预处理语句”-并非完全正确,因为PDO仅针对不支持该功能的驱动程序模拟预处理语句。 (2认同)

Gle*_*enn 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);
Run Code Online (Sandbox Code Playgroud)

Inject类很容易受到SQL注入攻击.查询与用户输入动态粘贴在一起.查询的目的是显示有关Bob的信息.基于用户输入的工资或奖金.但恶意用户操纵输入会破坏查询,方法是将相应的"或者"添加到where子句,以便返回所有内容,包括有关应该隐藏的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"));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

您不应该使用用户输入的字符串连接来构建SQL语句.它不仅易受注入攻击,而且还会对服务器产生缓存影响(语句更改,因此不太可能获得SQL语句缓存命中,而绑定示例始终运行相同的语句).

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

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"));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用与上一示例相同的输入运行此操作会显示恶意代码不起作用,因为没有与该字符串匹配的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=?
Run Code Online (Sandbox Code Playgroud)


小智 13

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

这是因为预准备语句首先"准备"SQL查询以找到有效的查询计划,并发送可能在以后从表单中获取的实际值 - 此时查询实际执行.

更多信息在这里:

准备语句和SQL注入


N.V*_*eta 6

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

天真的方法

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

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"
Run Code Online (Sandbox Code Playgroud)

例如,恶意用户输入可能导致SQLString等于"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

由于恶意用户,SQLString其中包含2条语句,其中第二条("DROP TABLE CUSTOMERS")会造成伤害。

准备的陈述

在这种情况下,由于查询和数据的分离,用户输入永远不会被视为SQL语句,因此永远不会执行。因此,注入的任何恶意SQL代码都不会造成危害。因此,"DROP TABLE CUSTOMERS"在上述情况下将永远不会执行。

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

  • 说数据与查询分开发送并且程序保持完整,仍然没有显示如何准确地防止损害。是不是因为查询部分在执行前经过了筛选?是因为查询从未执行过吗?这正是引发人们想了解幕后到底发生了什么的想法的思维过程。我相信我的回答回答了这个问题,并得到了赞成票,然后是反对票(我猜来自你),希望你能明白为什么这对其他人有用。 (4认同)
  • 真的吗?接受的答案并没有完全说明这一点? (2认同)

wul*_*pro 5

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

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

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