关于MySQL:如何在PHP中防止SQL注入?

How can I prevent SQL injection in PHP?

如果在不修改SQL查询的情况下插入用户输入,则应用程序容易受到SQL注入的攻击,如下例所示:

1
2
3
$unsafe_variable = $_POST['user_input'];

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

这是因为用户可以输入类似于value'); DROP TABLE table;--的内容,查询变成:

1
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

如何才能防止这种情况发生?


使用准备好的语句和参数化查询。这些SQL语句与任何参数分开发送到数据库服务器并由数据库服务器进行分析。这样攻击者就不可能注入恶意SQL。

您基本上有两个选择来实现这一点:

  • 使用PDO(对于任何支持的数据库驱动程序):

    1
    2
    3
    4
    5
    6
    7
    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');

    $stmt->execute(array('name' => $name));

    foreach ($stmt as $row) {
        // do something with $row
    }
  • 使用mysqli(用于mysql):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'

    $stmt->execute();

    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }
  • 如果您连接的数据库不是MySQL,那么您可以参考特定于驱动程序的第二个选项(例如,用于PostgreSQL的pg_prepare()pg_execute())。PDO是通用选项。

    正确设置连接

    注意,在使用PDO访问mysql数据库时,默认情况下不使用真正准备好的语句。要解决这个问题,您必须禁用对准备好的语句的模拟。使用PDO创建连接的一个示例是:

    1
    2
    3
    4
    $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    在上面的例子中,错误模式不是严格必要的,但是建议添加它。这样,当出现问题时,脚本就不会停止使用Fatal Error。开发人员有机会使用catch作为PDOExceptionthrown。

    但是,强制的是第一行setAttribute()命令PDO禁用模拟的已准备语句并使用实际的已准备语句。这可以确保在将语句和值发送到MySQL服务器之前,PHP不会对其进行解析(使可能的攻击者没有机会注入恶意SQL)。

    尽管您可以在构造函数的选项中设置charset,但需要注意的是,php(<5.3.6)的"旧"版本会自动忽略dsn中的charset参数。

    解释

    您传递给prepare的SQL语句是由数据库服务器解析和编译的。通过指定参数(上面示例中的?:name之类的命名参数),可以告诉数据库引擎要在哪里进行筛选。然后,当调用execute时,准备好的语句将与指定的参数值组合在一起。

    这里重要的是,参数值与已编译语句组合在一起,而不是与SQL字符串组合在一起。SQL注入的工作原理是,当脚本创建要发送到数据库的SQL时,诱使它包含恶意字符串。因此,通过将实际的SQL与参数分开发送,可以限制以您不想要的东西结束的风险。在使用准备好的语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数最终也可能成为数字)。在上面的示例中,如果$name变量包含'Sarah'; DELETE FROM employees,那么结果将只是搜索字符串"'Sarah'; DELETE FROM employees",您将不会得到一个空表。

    使用准备好的语句的另一个好处是,如果在同一个会话中多次执行同一个语句,那么它只会被解析和编译一次,从而提高了速度。

    哦,既然您问了如何为插入操作,下面是一个示例(使用pdo):

    1
    2
    3
    $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

    $preparedStatement->execute(array('column' => $unsafeValue));

    准备好的语句可以用于动态查询吗?

    尽管您仍然可以对查询参数使用准备好的语句,但动态查询本身的结构不能参数化,某些查询功能也不能参数化。

    对于这些特定的场景,最好使用限制可能值的白名单过滤器。

    1
    2
    3
    4
    5
    // Value whitelist
    // $dir can only be 'DESC' otherwise it will be 'ASC'
    if (empty($dir) || $dir !== 'DESC') {
       $dir = 'ASC';
    }


    Warning:
    This answer's sample code (like the question's sample code) uses PHP's MySQL extension, which was deprecated in PHP 5.5.0 and removed entirely in PHP 7.0.0.

    如果您使用的是最新版本的PHP,下面概述的mysql_real_escape_string选项将不再可用(尽管mysqli::escape_string是现代的等效选项)。现在,mysql_real_escape_string选项只对旧版本PHP上的遗留代码有意义。

    您有两个选项-转义unsafe_variable中的特殊字符,或者使用参数化查询。两者都可以保护您不受SQL注入的影响。参数化查询被认为是更好的做法,但在使用它之前,需要在PHP中更改为较新的MySQL扩展。

    我们先来介绍下冲击串。

    1
    2
    3
    4
    5
    6
    7
    8
    //Connect

    $unsafe_variable = $_POST["user-input"];
    $safe_variable = mysql_real_escape_string($unsafe_variable);

    mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable ."')");

    //Disconnect

    另请参见mysql_real_escape_string函数的详细信息。

    要使用参数化查询,需要使用mysqli而不是mysql函数。要重写您的示例,我们需要如下所示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?php
        $mysqli = new mysqli("server","username","password","database_name");

        // TODO - Check that connection was successful.

        $unsafe_variable = $_POST["user-input"];

        $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

        // TODO check that $stmt creation succeeded

        //"s" means the database expects a string
        $stmt->bind_param("s", $unsafe_variable);

        $stmt->execute();

        $stmt->close();

        $mysqli->close();
    ?>

    您想阅读的关键功能是mysqli::prepare

    另外,正如其他人所建议的,您可能会发现使用PDO之类的东西来增加一个抽象层是有用的/更容易的。

    请注意,您询问的案例相当简单,而更复杂的案例可能需要更复杂的方法。特别地:

    • 如果要根据用户输入更改SQL的结构,参数化查询将不会有帮助,并且所需的转义不在mysql_real_escape_string中涵盖。在这种情况下,最好通过白名单传递用户的输入,以确保只允许通过"安全"值。
    • 如果在条件中使用来自用户输入的整数,并采用mysql_real_escape_string方法,您将遇到下面注释中用多项式描述的问题。这种情况比较棘手,因为整数不会被引号包围,所以可以通过验证用户输入是否只包含数字来处理。
    • 可能还有其他我不知道的情况。您可能会发现,对于您可能遇到的一些更微妙的问题,这是一个有用的资源。


    这里的每一个答案只涵盖了问题的一部分。实际上,我们可以动态添加四个不同的查询部分:好的。

    • 一串
    • 一个数字
    • 标识符
    • 语法关键字。

    准备好的声明只包括其中两个。好的。

    但有时我们必须使查询更具动态性,同时添加运算符或标识符。所以,我们需要不同的保护技术。好的。

    一般来说,这种保护方法是基于白名单的。好的。

    在这种情况下,每个动态参数都应该在脚本中进行硬编码,并从该集合中进行选择。例如,要执行动态排序:好的。

    1
    2
    3
    4
    $orders  = array("name","price","qty"); // Field names
    $key     = array_search($_GET['sort'], $orders)); // See if we have such a name
    $orderby = $orders[$key]; // If not, first one will be set automatically. smart enuf :)
    $query   ="SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

    但是,还有另一种保护标识符的方法——转义。只要您引用了一个标识符,就可以通过将其加倍来转义内部的反勾号。好的。

    作为进一步的步骤,我们可以从准备好的语句中借用一个使用某些占位符(代表查询中实际值的代理)的绝妙想法,并发明另一种类型的占位符-标识符占位符。好的。

    因此,长话短说:这是一个占位符,没有准备好的声明可以被视为一个银弹。好的。

    因此,一般建议的措词可以是只要使用占位符向查询添加动态部分(当然,正确处理这些占位符),就可以确保查询是安全的。好的。

    尽管如此,SQL语法关键字(如ANDDESC等)仍然存在问题,但白名单似乎是这种情况下唯一的方法。好的。更新

    尽管关于SQL注入保护的最佳实践已经达成了共识,但仍然存在许多不好的实践。其中一些太过深植于PHP用户的头脑中。例如,在这个页面上(尽管大多数访问者看不见)有80多个删除的答案-所有这些答案都被社区删除,因为质量不好或推广了不好的和过时的做法。更糟糕的是,一些错误的答案并没有被删除,而是相当繁荣。好的。

    例如,还有(1)个(2)个(3)个(4)个答案(5),包括第二个最乐观的答案,建议您手动进行字符串转义,这是一种过时的方法,被证明是不安全的。好的。

    或者还有一个更好的答案,它只是建议了另一种字符串格式的方法,甚至夸耀它是终极的灵丹妙药。当然,事实并非如此。这个方法并不比常规的字符串格式更好,但是它保留了所有的缺点:它只适用于字符串,而且像任何其他手动格式一样,它本质上是可选的、非强制性的度量,容易出现任何类型的人为错误。好的。

    我认为所有这些都是因为一个古老的迷信,它得到了诸如owasp或php手册之类的权威机构的支持,这些权威机构宣称无论什么"逃避"和防止SQL注入之间都是平等的。好的。

    不管php手册说了多少年,*_escape_string绝不会使数据安全,而且从来没有打算这样做。除了对字符串以外的任何SQL部分都没有用处之外,手动转义是错误的,因为手动转义与自动转义相反。好的。

    OWASP使情况更糟,它强调对用户输入的转义,这完全是胡说八道:在注入保护的上下文中不应该有这样的词。每一个变量都有潜在的危险——不管来源如何!或者,换言之,每个变量都必须正确格式化,才能放入一个查询中——不管源代码是什么。重要的是目的地。当开发人员开始将绵羊与山羊分开(考虑某个特定变量是否"安全")时,他/她就迈出了走向灾难的第一步。更不用说,即使是措辞也暗示在入口点进行大量的转义,类似于非常神奇的引号功能——已经被轻视、否决和删除。好的。

    因此,与任何"转义"不同,准备好的语句实际上是防止SQL注入的措施(如果适用)。好的。

    如果你仍然不相信,这里有一个我写的一步一步的解释,"搭便车的SQL注入预防指南",在这里我详细地解释了所有这些问题,甚至编译了一个完全致力于坏做法及其披露的部分。好的。好啊。


    我建议使用pdo(php数据对象)来运行参数化的SQL查询。

    这不仅可以防止SQL注入,还可以加快查询速度。

    通过使用pdo而不是mysql_mysqli_pgsql_函数,你可以使你的应用程序更抽象地从数据库中提取出来,这种情况很少发生,你必须切换数据库提供者。


    使用PDO并准备好查询。

    ($connPDO对象)

    1
    2
    3
    4
    $stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
    $stmt->bindValue(':id', $id);
    $stmt->bindValue(':name', $name);
    $stmt->execute();


    正如你所看到的,人们建议你最多使用准备好的语句。这并不是错误的,但是当您的查询在每个进程中只执行一次时,会有轻微的性能损失。

    我正面临着这个问题,但我认为我以非常复杂的方式解决了这个问题——黑客用来避免使用引号的方式。我将它与模拟的准备语句结合使用。我使用它来防止各种可能的SQL注入攻击。

    我的方法是:

    • 如果您希望输入为整数,请确保它是真正的整数。在像php这样的变量类型语言中,这一点非常重要。例如,您可以使用这个非常简单但功能强大的解决方案:sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

    • 如果您还需要整型十六进制中的其他内容,那么它就是。如果您对它进行十六进制运算,您将完美地转义所有输入。在C/C++中,有一个称为EDCOX1的函数1,在PHP中,可以使用EDCOX1×2。

      不要担心转义字符串的大小将是其原始长度的2倍,因为即使使用mysql_real_escape_string,php也必须分配相同的容量((2*input_length)+1),这是相同的。

    • 这种十六进制方法通常在传输二进制数据时使用,但我看不出为什么不在所有数据上使用它来防止SQL注入攻击。请注意,您必须使用0x预先准备数据,或者使用mysql函数UNHEX

    例如,查询:

    1
    SELECT password FROM users WHERE name = 'root'

    将成为:

    1
    SELECT password FROM users WHERE name = 0x726f6f74

    1
    SELECT password FROM users WHERE name = UNHEX('726f6f74')

    十六进制是完美的逃避。无法注射。

    UNHEX函数和0x前缀之间的差异

    评论中有一些讨论,所以我最后想说清楚。这两种方法非常相似,但在某些方面略有不同:

    **0x**前缀只能用于char、varchar、text、block、binary等数据列。另外,如果您要插入一个空字符串,它的使用也有点复杂。您必须完全用''替换它,否则会出错。

    unrex()适用于任何列;您不必担心空字符串。

    十六进制方法通常用作攻击

    注意,这个十六进制方法经常被用作SQL注入攻击,其中整数就像字符串一样,只使用mysql_real_escape_string进行转义。这样就可以避免使用引号。

    例如,如果您只是这样做:

    1
    "SELECT title FROM article WHERE id =" . mysql_real_escape_string($_GET["id"])

    攻击很容易给你注射。考虑从脚本返回的以下注入代码:

    SELECT ... WHERE id = -1 union all select table_name from information_schema.tables

    现在只提取表结构:

    SELECT ... WHERE id = -1 union all select column_name from information_schema.column where table_name = 0x61727469636c65

    然后选择任何想要的数据。这不酷吗?

    但是,如果可注入站点的编码人员将对其进行十六进制处理,则不可能进行注入,因为查询如下所示:SELECT ... WHERE id = UNHEX('2d312075...3635')


    IMPORTANT

    The best way to prevent SQL Injection is to use Prepared Statements instead of escaping, as the accepted answer demonstrates.

    There are libraries such as Aura.Sql and EasyDB that allow developers to use prepared statements easier. To learn more about why prepared statements are better at stopping SQL injection, refer to this mysql_real_escape_string() bypass and recently fixed Unicode SQL Injection vulnerabilities in WordPress.

    注入预防-mysql_real_escape_string()

    PHP有一个特制的功能来防止这些攻击。你所需要做的就是用一口函数,mysql_real_escape_string

    mysql_real_escape_string接受一个将在MySQL查询中使用的字符串,并返回相同的字符串,安全地转义了所有SQL注入尝试。基本上,它将替换那些麻烦的引号(")一个用户可能输入一个MySQL安全的替代品,一个转义引号"。

    注意:您必须连接到数据库才能使用此功能!

    //连接mysql

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $name_bad ="' OR 1'";

    $name_bad = mysql_real_escape_string($name_bad);

    $query_bad ="SELECT * FROM customers WHERE username = '$name_bad'";
    echo"Escaped Bad Injection: <br />" . $query_bad ."<br />";


    $name_evil ="'; DELETE FROM customers WHERE 1 or username = '";

    $name_evil = mysql_real_escape_string($name_evil);

    $query_evil ="SELECT * FROM customers WHERE username = '$name_evil'";
    echo"Escaped Evil Injection: <br />" . $query_evil;

    您可以在mysql-sql注入预防中找到更多详细信息。


    Security Warning: This answer is not in line with security best practices. Escaping is inadequate to prevent SQL injection, use prepared statements instead. Use the strategy outlined below at your own risk. (Also, mysql_real_escape_string() was removed in PHP 7.)

    Deprecated Warning: The mysql extension is deprecated at this time. we recommend using the mysqli extension

    你可以这样做:

    1
    2
    $safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
    mysqli_query("INSERT INTO table (column) VALUES ('" . $safe_variable ."')");

    这并不能解决所有的问题,但它是一个很好的垫脚石。我遗漏了一些明显的项目,比如检查变量的存在、格式(数字、字母等)。


    无论你最终使用什么,确保你的输入没有被magic_quotes或其他一些含义良好的垃圾损坏,如果必要,运行stripslashes或其他任何东西对其进行消毒。


    参数化查询和输入验证是一种方法。尽管使用了mysql_real_escape_string(),但在许多情况下可能会发生SQL注入。

    这些示例易受SQL注入攻击:

    1
    2
    3
    $offset = isset($_GET['o']) ? $_GET['o'] : 0;
    $offset = mysql_real_escape_string($offset);
    RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

    1
    2
    3
    $order = isset($_GET['o']) ? $_GET['o'] : 'userid';
    $order = mysql_real_escape_string($order);
    RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

    在这两种情况下,都不能使用'来保护封装。

    源:意外的SQL注入(转义不够时)


    在我看来,通常防止PHP应用程序(或任何Web应用程序)中的SQL注入的最佳方法是考虑应用程序的体系结构。如果防止SQL注入的唯一方法是记住使用一种特殊的方法或函数,在每次与数据库交谈时都会做正确的事情,那么这是错误的。这样,直到您忘记在代码中的某个时刻正确地格式化查询,这只是一个时间问题。

    采用MVC模式和类似cakephp或codeigner的框架可能是正确的方法:诸如创建安全数据库查询之类的常见任务已经在这些框架中解决并集中实现。它们帮助您以一种合理的方式组织Web应用程序,使您更多地考虑加载和保存对象,而不是安全地构造单个SQL查询。


    从安全的角度来看,我更喜欢存储过程(MySQL从5.0开始就支持存储过程)——其优点是-

  • 大多数数据库(包括mysql)允许用户访问被限制为执行存储过程。细粒度的安全访问控制对于防止特权攻击升级很有用。这将防止受到损害的应用程序能够直接对数据库运行SQL。
  • 它们从应用程序抽象原始的SQL查询,因此应用程序可用的数据库结构信息较少。这使得人们更难理解数据库的底层结构并设计合适的攻击。
  • 它们只接受参数,因此参数化查询的优点就在这里。当然,在IMO中,您仍然需要清理输入,特别是在存储过程中使用动态SQL时。
  • 缺点是-

  • 它们(存储过程)很难维护,而且往往会快速增加。这使得管理它们成为一个问题。
  • 它们不太适合动态查询——如果它们被构建为接受动态代码作为参数,那么很多优点就被否定了。

  • 有许多方法可以防止SQL注入和其他SQL黑客攻击。你可以很容易地在互联网上找到它(谷歌搜索)。当然,PDO是一个很好的解决方案。但是,我想建议您使用一些很好的链接来防止SQL注入。

    什么是SQL注入以及如何防止

    用于SQL注入的PHP手册

    PHP中SQL注入和预防的微软解释

    还有其他一些,比如用mysql和php防止SQL注入

    现在,为什么您需要阻止您的查询进行SQL注入?

    我想让您知道:为什么我们要尝试通过下面的一个简短示例来防止SQL注入:

    登录验证匹配查询:

    1
    $query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."'";

    现在,如果有人(黑客)把

    1
    $_POST['email']= admin@emali.com' OR '1=1

    任何密码…

    查询将被解析到系统中,直到:

    1
    $query="select * from users where email='[email protected]' OR '1=1';

    另一部分将被丢弃。那么,会发生什么呢?非授权用户(黑客)可以以管理员身份登录,而不需要密码。现在,他可以做管理员/电子邮件人员可以做的任何事情。请看,如果不阻止SQL注入,这是非常危险的。


    我认为如果有人想使用php和mysql或其他数据库服务器:

  • 想想学习PDO(PHP数据对象)——它是一个数据库访问层,提供对多个数据库的统一访问方法。
  • 考虑学习mysqli
  • 使用本地PHP函数,如:strip_tags、mysql_real_escape_string或如果变量为numeric,则仅使用(int)$foo。在这里阅读关于PHP中变量类型的更多信息。如果您使用的是pdo或mysqli等库,请始终使用pdo::quote()和mysqli_real_escape_string()。
  • 库示例:

    PDO

    ----- No placeholders - ripe for SQL injection! It's bad

    1
    $request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

    ----- Unnamed placeholders

    1
    $request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

    ----- Named placeholders

    1
    $request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

    ---迈克斯利

    1
    2
    3
    4
    5
    6
    7
    8
    $request = $mysqliConnection->prepare('
           SELECT * FROM trainers
           WHERE name = ?
           AND email = ?
           AND last_login > ?'
    );

        $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
        $query->execute();

    附笔:

    PDO轻松赢得了这场战斗。支持12人不同的数据库驱动程序和命名参数,我们可以忽略性能损失小,并适应其API。从安全站在观点上,只要开发者使用它们,它们都是安全的。它们的使用方式

    但是,虽然PDO和MySQLI都很快,但MySQLI的性能基准测试速度显著加快——对于未准备好的测试,约为2.5%报表,准备好的约6.5%。

    请测试对数据库的每个查询——这是防止注入的更好方法。


    如果可能,请强制转换参数的类型。但它只适用于简单类型,如int、bool和float。

    1
    2
    3
    4
    5
    $unsafe_variable = $_POST['user_id'];

    $safe_variable = (int)$unsafe_variable ;

    mysqli_query($conn,"INSERT INTO table (column) VALUES ('" . $safe_variable ."')");


    如果您想利用缓存引擎,比如redis或memcached,那么dalmp可能是一个选择。它使用纯mysqli。检查:使用php的mysql的dalmp数据库抽象层。

    另外,您可以在准备查询之前"准备"参数,这样您就可以构建动态查询,并且在最后有一个完全准备好的语句查询。使用PHP的MySQL的DALMP数据库抽象层。


    对于那些不确定如何使用PDO(来自mysql_函数)的人,我制作了一个非常简单的PDO包装器,它是一个文件。它的存在是为了展示应用程序需要做的所有普通事情是多么容易。使用PostgreSQL、MySQL和SQLite。

    基本上,在阅读本手册的同时阅读本手册,了解如何将PDO函数用于现实生活中,使以您想要的格式存储和检索值变得简单。

    I want a single column

    1
    $count = DB::column('SELECT COUNT(*) FROM `user`);

    I want an array(key => value) results (i.e. for making a selectbox)

    1
    $pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

    I want a single row result

    1
    $user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

    I want an array of results

    1
    $banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

    使用这个php函数mysql_escape_string()可以快速得到一个很好的预防。

    例如:

    1
    SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

    mysql_escape_string—转义字符串以在mysql查询中使用

    为了更多的预防,您可以在末尾添加…

    1
    wHERE 1=1   or  LIMIT 1

    最后你得到:

    1
    SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

    一些在SQL语句中转义特殊字符的准则。

    不要使用mysql,这个扩展不推荐使用,请使用mysqli或pdo。

    米斯利

    对于手动转义字符串中的特殊字符,可以使用mysqli_real_escape_string函数。除非使用mysqli_set_charset设置了正确的字符集,否则函数将无法正常工作。

    例子:

    1
    2
    3
    4
    5
    $mysqli = new mysqli( 'host', 'user', 'password', 'database' );
    $mysqli->set_charset( 'charset');

    $string = $mysqli->real_escape_string( $string );
    $mysqli->query("INSERT INTO table (column) VALUES ('$string')" );

    对于使用准备好的语句自动转义值,请使用mysqli_prepare和mysqli_stmt_bind_param,其中必须为相应的绑定变量提供类型以进行适当的转换:

    例子:

    1
    2
    3
    4
    5
    $stmt = $mysqli->prepare("INSERT INTO table ( column1, column2 ) VALUES (?,?)" );

    $stmt->bind_param("is", $integer, $string );

    $stmt->execute();

    无论您使用的是准备好的语句还是mysqli_real_escape_字符串,您都必须知道您使用的输入数据的类型。

    因此,如果使用准备好的语句,则必须为mysqli_stmt_bind_param函数指定变量的类型。

    而mysqli_real_escape_字符串的使用,正如其名称所说,是为了在字符串中转义特殊字符,因此它不会使整数安全。此函数的目的是防止破坏SQL语句中的字符串,以及它可能导致的数据库损坏。mysqli_real_escape_string在正确使用时是一个有用的函数,特别是与sprintf结合使用时。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $string ="x' OR name LIKE '%John%";
    $integer = '5 OR id != 0';

    $query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

    echo $query;
    // SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

    $integer = '99999999999999999999';
    $query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer );

    echo $query;
    // SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647


    通过在数据库本身中授予适当的权限,可以解决这个问题的简单替代方案。例如:如果您使用的是MySQL数据库,那么可以通过终端或提供的UI进入数据库,然后按照以下命令操作:

    1
     GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

    这将限制用户仅使用指定的查询进行限制。删除删除权限,这样就永远不会从从从PHP页面激发的查询中删除数据。第二件事是刷新权限,以便MySQL刷新权限和更新。

    1
    FLUSH PRIVILEGES;

    有关刷新的详细信息。

    若要查看用户的当前权限,请启动以下查询。

    1
    select * from mysql.user where User='username';

    了解有关Grant的更多信息。


    我使用三种不同的方法来防止我的Web应用程序容易受到SQL注入的攻击。

  • 使用mysql_real_escape_string(),它是php中的一个预先定义的函数,此代码对以下字符加反斜杠:\x00

    \'"\x1a。将输入值作为参数传递,以最小化SQL注入的机会。
  • 最先进的方法是使用PDO。
  • 我希望这对你有帮助。

    考虑以下查询:

    $iId = mysql_real_escape_string("1 OR 1=1");
    $sSql ="SELECT * FROM table WHERE id = $iId";

    mysql_real_escape_string()在此不受保护。如果在查询中的变量周围使用单引号(""),则可以防止出现这种情况。下面是一个解决方案:

    $iId = (int) mysql_real_escape_string("1 OR 1=1");
    $sSql ="SELECT * FROM table WHERE id = $iId";

    这个问题有一些很好的答案。

    我建议,使用PDO是最好的选择。

    编辑:

    从php 5.5.0开始,不推荐使用mysql_real_escape_string()。使用mysqli或pdo。

    mysql_real_escape_string()的替代方法是

    1
    string mysqli_real_escape_string ( mysqli $link , string $escapestr )

    例子:

    1
    2
    $iId = $mysqli->real_escape_string("1 OR 1=1");
    $mysqli->query("SELECT * FROM table WHERE id = $iId");

    关于许多有用的答案,我希望为这个线程添加一些值。SQL注入是一种可以通过用户输入(由用户填充的输入,然后在查询中使用)来完成的攻击,SQL注入模式是正确的查询语法,而我们可以称之为:错误的查询是由于错误的原因,我们假定可能有一个错误的人试图获取影响到数据库的机密信息(绕过访问控制)。REE安全原则(保密性、完整性、可用性)。好的。

    现在,我们的重点是防止安全威胁,如SQL注入攻击,问题是(如何防止使用PHP的SQL注入攻击),更现实一点,数据过滤或清除输入数据是在这种查询中使用用户输入数据的情况下,使用PHP或任何其他编程语言不是这样的情况,或是按照更多的P建议。eople要使用现代技术,如准备好的语句或当前支持SQL注入预防的任何其他工具,是否认为这些工具不再可用?如何保护应用程序?好的。

    我针对SQL注入的方法是:在将用户输入数据发送到数据库之前(在任何查询中使用它之前),清除这些数据。好的。

    数据筛选(将不安全数据转换为安全数据)考虑到PDO和mysqli不可用,您如何保护您的应用程序?你强迫我使用它们吗?除了PHP以外的其他语言呢?我更愿意提供一般性的想法,因为它可以用于更广泛的边界,而不仅仅是特定的语言。好的。

  • SQL用户(限制用户权限):最常见的SQL操作是(select、update、insert),那么,为什么要向不需要更新权限的用户授予更新权限呢?例如,登录和搜索页面只使用select,那么,为什么在这些具有高权限的页面中使用db用户呢?规则:不要为所有权限创建一个数据库用户,对于所有SQL操作,您可以创建类似(deluser、selectuser、updateuser)的方案作为用户名,以便于使用。
  • 见最小特权原则好的。

  • 数据过滤:在构建任何查询之前,应该验证和过滤用户输入,对于程序员来说,为每个用户输入变量定义一些属性很重要:数据类型、数据模式和数据长度。(x和y)之间的数字字段必须使用精确规则进行精确验证,对于字符串(文本)字段:模式就是这样,例如,用户名必须只包含一些字符,例如[a-za-z0-9_u-]长度在(x和n)之间变化,其中x和n(整数,x<=n)。规则:创建精确的过滤器和验证规则是我的最佳实践。好的。

  • 使用其他工具:这里,我也同意您的观点,即准备好的语句(参数化查询)和存储过程,这里的缺点是这些方法需要高级的技能,大多数用户不具备这些技能,这里的基本思想是区分SQL查询和内部使用的数据,这两种方法甚至可以用于安全数据,因为这里的用户输入数据不会向原始查询添加任何内容,例如(any或x=x)。有关详细信息,请阅读OWASP SQL注入预防技术手册。好的。

  • 现在,如果您是一个高级用户,可以开始使用这种防御,但是对于初学者来说,如果他们不能快速实现存储过程并准备好语句,那么最好对输入数据进行尽可能多的过滤。好的。

    最后,让我们考虑用户在下面发送此文本,而不是输入其用户名:好的。

    1
    [1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

    可以在没有任何准备好的语句和存储过程的情况下尽早检查此输入,但为了安全起见,可以在用户数据筛选和验证之后开始使用它们。好的。

    最后一点是检测需要更多工作和复杂性的意外行为;不建议用于普通Web应用程序。上述用户输入中的意外行为是select、union、if、substring、benchmark、sha、root,一旦检测到这些词,就可以避免输入。好的。更新1:

    一位用户评论说这篇文章没用,好吧!以下是owasp.org提供的内容:好的。

    Primary defenses:

    Option #1: Use of Prepared Statements (Parameterized Queries)
    Option #2: Use of Stored Procedures
    Option #3: Escaping all User Supplied Input

    Additional defenses:

    Also Enforce: Least Privilege
    Also Perform: White List Input Validation

    Ok.

    正如你可能知道的,声称一篇文章应该得到有效论点的支持,至少一个参考!否则,它被认为是一种攻击和坏主张!好的。更新2:

    从PHP手册中,php:Prepared Statements-Manual:好的。

    Escaping and SQL injection

    Ok.

    Bound variables will be escaped automatically by the server. The
    server inserts their escaped values at the appropriate places into the
    statement template before execution. A hint must be provided to the
    server for the type of bound variable, to create an appropriate
    conversion. See the mysqli_stmt_bind_param() function for more
    information.

    Ok.

    The automatic escaping of values within the server is sometimes
    considered a security feature to prevent SQL injection. The same
    degree of security can be achieved with non-prepared statements if
    input values are escaped correctly.

    Ok.

    更新3:

    我创建了一个测试案例,了解在使用准备好的语句时,pdo和mysql i如何将查询发送到mysql服务器:好的。

    PDO:好的。

    1
    2
    3
    4
    $user ="''1''"; //Malicious keyword
    $sql = 'SELECT * FROM awa_user WHERE userame =:username';
    $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    $sth->execute(array(':username' => $user));

    查询日志:好的。

    1
    2
        189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
        189 Quit

    MySQLi:好的。

    1
    2
    3
    4
    $stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
    $stmt->bind_param("s", $user);
    $user ="''1''";
    $stmt->execute();

    查询日志:好的。

    1
    2
    3
        188 Prepare   SELECT * FROM awa_user WHERE username =?
        188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
        188 Quit

    很明显,一个准备好的语句也在转义数据,而不是别的。

    如上述声明The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly所述,这证明了在发送任何查询之前,intval()等数据验证对于整数值是一个好主意,另外,在发送查询之前防止恶意用户数据是正确有效的方法。好的。

    请参阅这个问题了解更多细节:PDO向MySQL发送原始查询,而MySQLI则发送准备好的查询,两者都会产生相同的结果。好的。

    参考文献:好的。

  • SQL注入备忘表
  • SQL注入
  • 信息安全
  • 安全原则
  • 数据验证
  • 好啊。


    一个简单的方法是使用一个PHP框架,比如codeigner或laravel,它有内置的功能,比如过滤和活动记录,这样您就不必担心这些细微差别。


    **警告:此答案中描述的方法仅适用于非常特定的场景,而且不安全,因为SQL注入攻击不仅依赖于能够注入X=Y.*。*

    如果攻击者试图通过php的$_GET变量或url的查询字符串侵入表单,那么如果它们不安全,您将能够捕获它们。

    1
    2
    RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
    RewriteRule ^(.*) ^/track.php

    因为1=12=21=22=11+1=2等……是攻击者的SQL数据库的常见问题。也许它也被许多黑客应用程序使用。

    但是您必须小心,不要重写来自您站点的安全查询。上面的代码为您提供了一个提示,可以重写或重定向(这取决于您)将特定的动态查询字符串破解到一个将存储攻击者的IP地址、甚至其cookie、历史记录、浏览器或任何其他敏感信息的页面中,以便您以后可以通过禁止其帐户或联系当局来处理它们。


    有很多关于php和mysql的答案,但下面是php和oracle的代码,用于防止SQL注入以及定期使用oci8驱动程序:

    1
    2
    3
    4
    $conn = oci_connect($username, $password, $connection_string);
    $stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
    oci_bind_by_name($stmt, ':xx', $fieldval);
    oci_execute($stmt);


    一个好主意是使用"对象关系映射器",如Deirm:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    $user = ORM::for_table('user')
    ->where_equal('username', 'j4mie')
    ->find_one();

    $user->first_name = 'Jamie';
    $user->save();

    $tweets = ORM::for_table('tweet')
        ->select('tweet.*')
        ->join('user', array(
            'user.id', '=', 'tweet.user_id'
        ))
        ->where_equal('user.username', 'j4mie')
        ->find_many();

    foreach ($tweets as $tweet) {
        echo $tweet->text;
    }

    它不仅可以避免SQL注入,还可以避免语法错误!还支持模型集合,方法链接用于一次对多个结果和多个连接进行筛选或应用操作。


    使用pdo和mysqli是防止SQL注入的一个很好的实践,但是如果您真的想使用mysql函数和查询,最好使用

    mysql_real_escape_字符串

    1
    $unsafe_variable = mysql_real_escape_string($_POST['user_input']);

    有更多的功能可以防止这种情况发生:像identify——如果输入是字符串、数字、字符或数组,那么有很多内置函数可以检测到这种情况。此外,最好使用这些函数检查输入数据。

    ISSH字符串

    1
    $unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

    IS-数值的

    1
    $unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

    使用这些函数检查输入数据比使用mysql_real_escape_string要好得多。


    几年前我写过这个小函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    function sqlvprintf($query, $args)
    {
        global $DB_LINK;
        $ctr = 0;
        ensureConnection(); // Connect to database if not connected already.
        $values = array();
        foreach ($args as $value)
        {
            if (is_string($value))
            {
                $value ="'" . mysqli_real_escape_string($DB_LINK, $value) ."'";
            }
            else if (is_null($value))
            {
                $value = 'NULL';
            }
            else if (!is_int($value) && !is_float($value))
            {
                die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
            }
            $values[] = $value;
            $ctr++;
        }
        $query = preg_replace_callback(
            '/{(\\d+)}/',
            function($match) use ($values)
            {
                if (isset($values[$match[1]]))
                {
                    return $values[$match[1]];
                }
                else
                {
                    return $match[0];
                }
            },
            $query
        );
        return $query;
    }

    function runEscapedQuery($preparedQuery /*, ...*/)
    {
        $params = array_slice(func_get_args(), 1);
        $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.  
        return $results;
    }

    这允许在一行C-ish字符串中运行语句。格式如下:

    1
    runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

    考虑到变量类型,它将逃逸。如果您试图参数化表、列名,它将失败,因为它将每个字符串都放在引号中,这是一个无效的语法。

    安全更新:以前的str_replace版本允许通过向用户数据中添加令牌进行注入。如果替换包含这些令牌,则此preg_replace_callback版本不会导致问题。