如何在PHP中阻止SQL注入?

Andrew G. Johnson 2779 php mysql sql security sql-injection

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

$unsafe_variable = $_POST['user_input']; 

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

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

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

可以采取哪些措施来防止这种情况发生?

Theo.. 8353

使用预准备语句和参数化查询.这些是由数据库服务器与任何参数分开发送和解析的SQL语句.这样攻击者就无法注入恶意SQL.

你基本上有两个选择来实现这个目标:

  1. 使用PDO(适用于任何支持的数据库驱动程序):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. 使用MySQLi(用于MySQL):

    $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以外的数据库,则可以参考特定于驱动程序的第二个选项(例如pg_prepare(),pg_execute()对于PostgreSQL).PDO是通用选项.

正确设置连接

请注意,在使用PDO访问MySQL数据库时,默认情况下不使用实际准备好的语句.要解决此问题,您必须禁用预准备语句的模拟.使用PDO创建连接的示例是:

$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任何thrown为PDOExceptions的错误的机会.

什么是强制性的,但是,是第一setAttribute()线,它告诉PDO禁用模拟预处理语句和使用真正准备好的语句.这可以确保PHP在将语句和值发送到MySQL服务器之前不会解析它(使攻击者可能无法注入恶意SQL).

虽然您可以charset在构造函数的选项中进行设置,但重要的是要注意PHP的旧版本(<5.3.6)默认忽略 DSN中的charset参数.

说明

会发生什么是您传递给的SQL语句prepare由数据库服务器解析和编译.通过指定参数(如上例中的a ?或命名参数:name),您可以告诉数据库引擎要过滤的位置.然后,当您调用时execute,预准备语句将与您指定的参数值组合.

这里重要的是参数值与编译语句结合,而不是SQL字符串.SQL注入通过在创建SQL以发送到数据库时欺骗脚本来包含恶意字符串.因此,通过从参数中单独发送实际SQL,可以限制最终出现您不想要的内容的风险.使用预准备语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数最终也可能作为数字).在上面的示例中,如果$name变量包含'Sarah'; DELETE FROM employees结果,那么只需要搜索字符串"'Sarah'; DELETE FROM employees",您就不会得到一个空表.

使用预准备语句的另一个好处是,如果在同一个会话中多次执行相同的语句,它只会被解析和编译一次,从而为您带来一些速度提升.

哦,既然你问过如何为插入操作,这是一个例子(使用PDO):

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

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

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

虽然您仍然可以为查询参数使用预准备语句,但动态查询本身的结构无法进行参数化,并且某些查询功能无法进行参数化.

对于这些特定方案,最好的办法是使用限制可能值的白名单过滤器.

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

  • 只是添加,因为我没有在其他任何地方看到它,另一道防线是[web应用程序防火墙](https://www.owasp.org/index.php/Web_Application_Firewall)(WAF)可以有规则是设置为寻找SQL注入攻击: (61认同)
  • 另外,mysql_query的官方文档只允许执行一个查询,所以除了之外的任何其他查询; 被忽略了.即使已经弃用,PHP 5.5.0下也有很多系统可以使用这个功能.http://php.net/manual/en/function.mysql-query.php (32认同)
  • 这个答案缺乏对准备好的陈述的解释 - 一件事 - 如果你在请求期间使用了大量准备好的陈述,那么它会受到性能影响,有时甚至会导致10倍的性能损失.更好的情况是使用PDO参数绑定关闭,但语句准备关闭. (12认同)
  • 这是一个坏习惯,但它是一个问题后解决方案:不仅用于SQL注入,而且用于任何类型的注入(例如,在F3框架v2中有一个视图模板注入孔)如果你有一个现成的旧网站或应用程序正在遭受从注入缺陷来看,一种解决方案是在引导程序中使用转义值重新分配超级全局预定义变量(如$ _POST)的值.通过PDO,仍然可以转义(也适用于今天的框架):substr($ pdo-> quote($ str,\ PDO :: PARAM_STR),1,-1) (11认同)
  • 使用PDO更好,如果您使用直接查询,请确保使用mysqli :: escape_string (4认同)
  • @Alix这在理论上听起来是个好主意,但有时值需要不同类型的转义,例如SQL和HTML (2认同)

Matt Sheppar.. 1578

警告: 这个答案的示例代码(如问题的示例代码)使用PHP的MySQL扩展,在PHP 5.5.0中已弃用,并在PHP 7.0.0中完全删除.

如果您使用的是最新版本的PHP,则mysql_real_escape_string下面列出的选项将不再可用(尽管mysqli::escape_string它是现代版本).这些天,该mysql_real_escape_string选项只适用于旧版PHP上的遗留代码.


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

我们将首先覆盖较低影响的字符串.

//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函数.要重写您的示例,我们需要类似以下内容.

<?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方法,您将在下面的注释中遇到Polynomial描述的问题.这种情况比较棘手,因为整数不会被引号括起来,所以你可以通过验证用户输入只包含数字来处理.
  • 可能还有其他我不知道的情况.您可能会发现是一个有用的资源,可以解决您可能遇到的一些更微妙的问题.

  • @peimanF.保持使用参数化查询的良好实践,即使在本地项目上也是如此.使用参数化查询可以保证**不会有SQL注入.但请记住,您应该清理数据以避免虚假检索(例如XSS注入,例如将HTML代码放入文本中),例如`htmlentities` (5认同)
  • @peimanF.参数化查询和绑定值的良好实践,但真正的转义字符串现在是好的 (2认同)

用户甲.. 1008

这里的每个答案都只包含部分问题.事实上,我们可以动态添加四个不同的查询部分: -

  • 一个字符串
  • 一个号码
  • 标识符
  • 语法关键字.

准备好的陈述只涵盖其中两个.

但有时我们必须使我们的查询更加动态,同时添加运算符或标识符.因此,我们需要不同的保护技术.

通常,这种保护方法基于白名单.

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

$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语法关键字存在问题(例如AND,DESC等等),但在这种情况下,白名单似乎是唯一的方法.

更新

尽管对SQL注入保护的最佳实践存在普遍的一致意见,但仍然存在许多不良实践.其中一些根深蒂固的PHP用户心中.例如,在这个页面上(虽然大多数访问者看不到)超过80个已删除的答案 - 由于质量差或促进不良和过时的做法而被社区删除.更糟糕的是,一些不好的答案不会被删除,而是会更加繁荣.

例如,(1) 是(2) 仍然(3) 许多(4) 答案(5),包括第二个最受欢迎的答案,建议你手动字符串转义 - 一种被证明是不安全的过时方法.

或者有一个稍微好一点的答案,它表明了另一种字符串格式化的方法,甚至将它作为最终的灵丹妙药.当然,事实并非如此.这种方法并不比常规的字符串格式更好,但它保留了它的所有缺点:它只适用于字符串,和任何其他手动格式一样,它基本上是可选的,非强制性的措施,容易出现任何类型的人为错误.

我认为这一切都是因为一个非常古老的迷信,得到了诸如OWASPPHP手册等权威机构的支持,它宣称了"逃避"和防止SQL注入之间的平等.

无论PHP手册多年来所说的是什么,*_escape_string决不会使数据安全,而且从来没有打算过.除了对字符串以外的任何SQL部分无用之外,手动转义是错误的,因为它是手动的,与自动化相反.

并且OWASP使情况变得更糟,强调逃避用户输入这完全是胡说八道:在注入保护的背景下应该没有这样的词.每个变量都有潜在危险 - 无论来源如何!或者,换句话说 - 每个变量都必须正确格式化以便放入查询中 - 无论来源是什么.这是重要的目的地.在开发人员开始将绵羊与山羊分开的那一刻(考虑某些特定变量是否"安全")他/她迈出了他/她迈向灾难的第一步.更不用说即使是措辞也表明在入口点大量逃避,类似于非常神奇的引号功能 - 已经被鄙视,弃用和删除.

因此,与无论"逃离",准备好的语句,确实从SQL注入(如适用)保护措施.

如果您仍然不相信,这里是我写的"Hitchhiker的SQL注入预防指南"的逐步解释,我详细解释了所有这些问题,甚至编写了一个完全致力于不良做法及其披露的部分.

  • @Sablefoste你不需要在这里列入白名单.任何消毒都是多余的.遵循的规则越少,您犯的错误就越少.虽然您可以进行任何验证,但是为了应用程序逻辑而不是数据库. (20认同)
  • 伟大的,经过深思熟虑的文章.我可能会补充说,使用PHP的Sanitize过滤器是一种(但不完全是)一种白色列表.例如,`FILTER_SANITIZE_NUMBER_INT`只允许数字字符,因此白名单字符,而不是整个字符串.结合准备好的陈述,它是一个很好的"腰带和吊带"的方法. (10认同)

Kibbee.. 799

我建议使用PDO(PHP数据对象)来运行参数化SQL查询.

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

通过使用PDO,而不是mysql_,mysqli_pgsql_功能,你让你的应用程序从数据库中多了几分抽象的,因为你必须选择数据库提供者很少发生.

  • 使用参数化查询可以加快查询速度.从技术上讲,mysqli的速度可能会更快.服务器响应查询所花费的实际时间超过了因使用包装器而可能发生的时间差异.但是mysqli与数据库绑定在一起.如果要使用其他数据库引擎,则必须更改使用mysqli的所有调用.PDO不是这样. (4认同)
  • 不是PDO为MySQL DB包装mysqli?在这种情况下,它肯定不会比mysqli更快.我仍然会推荐它.这是一个比mysqli API更好的界面. (2认同)
  • 不幸的是,PDO不支持动态`order by :( (2认同)

Imran.. 591

使用PDO和准备的查询.

($conn是一个PDO对象)

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

  • 来自[wikipedia](https://en.wikipedia.org/wiki/Prepared_statement):准备好的语句对SQL注入具有弹性,因为稍后使用不同协议传输的参数值无需正确转义.如果原始语句模板不是从外部输入派生的,则不能进行SQL注入. (8认同)

Zaffy.. 523

如您所见,人们建议您最多使用预备语句.这没有错,但是当每个进程执行一次查询时,会有轻微的性能损失.

我正面临着这个问题,但我认为我以非常复杂的方式解决了这个问题- 黑客用来避免使用引号的方式.我将它与模拟的预处理语句结合使用.我用它来预防所有种类的可能的SQL注入攻击.

我的方法:

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

  • 如果你期望整数十六进制的其他东西.如果你把它变成十六进制,你将完全逃脱所有输入.在C/C++中有一个名为的函数mysql_hex_string(),在PHP中你可以使用bin2hex().

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

  • 传输二进制数据时经常使用这种十六进制方法,但我认为没有理由不在所有数据上使用它来防止SQL注入攻击.请注意,您必须0x使用MySQL函数或使用MySQL函数UNHEX.

所以,例如,查询:

SELECT password FROM users WHERE name = 'root'

会变成:

SELECT password FROM users WHERE name = 0x726f6f74

要么

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

Hex是完美的逃脱.无法注射.

UNHEX函数和0x前缀之间的区别

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

**0x**前缀只能用于char,varchar,text,block,binary等数据列.
此外,如果您要插入空字符串,它的使用会有点复杂.您必须完全替换它'',否则您将收到错误.

UNHEX()适用于任何列; 你不必担心空字符串.


Hex方法通常用作攻击

请注意,这种十六进制方法通常用作SQL注入攻击,其中整数就像字符串一样,只是转义为mysql_real_escape_string.然后你可以避免使用引号.

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

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

攻击可以很容易地注入你.考虑从脚本返回的以下注入代码:

SELECT ... WHERE id = -1 union来自information_schema.tables的所有select table_name

现在只提取表结构:

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

然后只需选择想要的任何数据.不是很酷吗?

但是如果可注射部位的编码器会使其变为十六进制,则无法进行注入,因为查询看起来像这样: SELECT ... WHERE id = UNHEX('2d312075...3635')

  • @YourCommonSense你不明白这个概念...如果你想在mysql中使用字符串你引用它就像这个`'root'`或者你可以将它改为`0x726f6f74`但是如果你想要一个数字并将其作为字符串发送你可能会写'42'不是CHAR(42)...''''''''''''''''''''''''''''''''''''''''''''' (10认同)
  • @YourCommonSense我无话可说......只是大声笑......如果您仍想在数字字段上尝试十六进制,请参阅第二条评论.我跟你打赌,它会起作用. (7认同)
  • @YourCommonSense你还不明白吗?你不能使用0x和concat,因为如果字符串是空的,你将以错误结束.如果你想要简单的替代查询,请试试这个`SELECT title FROM article WHERE id = UNHEX('.bin2hex($ _ GET ["id"]).')` (5认同)
  • @SumitGupta是的,你做到了.MySQL不与`+`连接,而是与`CONCAT`连接.性能:我认为它不会影响性能,因为mysql必须解析数据,如果origin是string或hex则无关紧要 (4认同)
  • @YourCommonSense您遇到什么错误?请明确点. (4认同)
  • 在多次应用查询的情况下,准备语句比基于字符串连接的sql查询慢,最多为2:1(在同一时间内,您可以执行2次基于字符串的查询而不是预处理语句)使用本地托管的mysql 5.x通过本地unix socket连接.这就是我更喜欢基于字符串的sql而不是预处理语句的原因(尽管与内存中的kv存储相比,sql总是很慢).此外它(=已发布的答案)是可靠的概念 - 了解它是如何工作的,你会明白为什么. (2认同)
  • [`mysql_real_escape_string`](http://php.net/manual/en/function.mysql-real-escape-string.php)现已弃用,因此它不再是一个可行的选项.它将来会从PHP中删除.最好转到PHP或MySQL人推荐的内容. (2认同)

rahularyansh.. 474

重要

防止SQL注入的最佳方法是使用准备语句 而不是转义,如接受的答案所示.

有一些库,如Aura.SqlEasyDB,允许开发人员更容易地使用准备语句.要了解有关为什么预处理语句更好地停止SQL注入的详细信息,请参阅mysql_real_escape_string()绕过以及最近修复的WordPress中的Unicode SQL注入漏洞.

注入预防 - mysql_real_escape_string()

PHP有一个特殊的功能来防止这些攻击.你需要做的只是使用一口功能,mysql_real_escape_string.

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

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

//连接MySQL

$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注入预防.

  • 这是使用传统mysql扩展可以做的最好的事情.对于新代码,建议您切换到mysqli或PDO. (28认同)
  • **警告!**`mysql_real_escape_string()`[不是绝对可靠](http://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string). (14认同)
  • [`mysql_real_escape_string`](http://php.net/manual/en/function.mysql-real-escape-string.php)现已弃用,因此它不再是一个可行的选项.它将来会从PHP中删除.最好转到PHP或MySQL人推荐的内容. (9认同)
  • 我不同意这个"防止这些攻击的特殊功能".我认为`mysql_real_escape_string`的目的是允许为每个输入数据字符串构建正确的SQL查询.预防sql-injection是这个功能的副作用. (7认同)
  • 你不使用函数来写正确的输入数据字符串.你只需编写不需要转义或已经转义的正确的.mysql_real_escape_string()可能是为了你提到的目的而设计的,但它唯一的价值就是防止注入. (4认同)

Tanerax.. 435

安全警告:此答案与安全最佳实践不符.转义不足以阻止SQL注入,而是使用预准备语句.使用下面列出的策略需要您自担风险.(另外,mysql_real_escape_string()在PHP 7中删除了.)

推荐使用警告:此时不推荐使用mysql扩展.我们建议使用mysqli扩展

你可以做一些基本的事情:

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

这不会解决所有问题,但它是一个非常好的垫脚石.我省略了明显的项目,例如检查变量的存在,格式(数字,字母等).

  • 我试过你的例子,这对我来说很好.你能清楚"这不会解决所有问题" (26认同)
  • **警告!**`mysql_real_escape_string()`[不是绝对可靠](http://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string). (19认同)
  • 如果你不引用字符串,它仍然是可注射的.以`$ q ="SELECT col FROM tbl WHERE x = $ safe_var";`为例.在这种情况下,由于缺少引号,将`$ safe_var`设置为`1 UNION SELECT password FROM users`.也可以使用`CONCAT`和`CHR`将字符串注入查询. (13认同)
  • [`mysql_real_escape_string`](http://php.net/manual/en/function.mysql-real-escape-string.php)现已弃用,因此它不再是一个可行的选项.它将来会从PHP中删除.最好转到PHP或MySQL人推荐的内容. (6认同)

Rob.. 365

无论你最终使用什么,确保你检查你的输入还没有被magic_quotes其他好的垃圾或其他一些好的垃圾所破坏,如果有必要的话,可以通过它stripslashes或任何消毒它.

  • 从PHP 5.4开始,被称为"魔术引号"的憎恶已被[杀死](http://php.net/manual/en/security.magicquotes.php).并且很好地摆脱了糟糕的垃圾. (20认同)
  • 确实; 使用magic_quotes运行只会鼓励糟糕的练习.但是,有时您无法始终将环境控制到该级别 - 您无权管理服务器,或者您的应用程序必须与(颤抖)依赖此类配置的应用程序共存.出于这些原因,编写可移植应用程序是件好事 - 尽管如果你控制部署环境显然会浪费精力,例如因为它是一个内部应用程序,或者只是在你的特定环境中使用. (9认同)

Cedric.. 348

参数化查询和输入验证是要走的路.即使mysql_real_escape_string()已经使用了SQL注入,也有许多情况可能会发生.

这些示例容易受到SQL注入:

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

要么

$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注入(当转义不够时)


Johannes Fah.. 297

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

采用MVC模式和像CakePHPCodeIgniter这样的框架可能是正确的方法:创建安全数据库查询等常见任务已经在这样的框架中得到解决和集中实现.它们可以帮助您以合理的方式组织Web应用程序,并使您更多地考虑加载和保存对象,而不是安全地构建单个SQL查询.

  • 我认为你的第一段很重要.理解是关键.此外,每个人都不是为公司工作.对于大量的人来说,框架实际上违背了*理解*的想法.在截止日期前工作时,与基本面的关系可能并不重要,但是那些自己动手的人喜欢弄脏自己.框架开发人员没有那么特权,其他人都必须低头并假设他们永远不会犯错误.做出决策的权力仍然很重要.谁能说我的框架将来不会取代其他一些计划? (4认同)

Nikhil.. 283

从安全角度来看,我赞成存储过程(MySQL自5.0以来一直支持存储过程) - 优点是 -

  1. 大多数数据库(包括MySQL)都允许将用户访问限制为执行存储过程.细粒度的安全访问控制有助于防止特权攻击升级.这可以防止受感染的应用程序直接对数据库运行SQL.
  2. 它们从应用程序中抽象出原始SQL查询,因此应用程序可以使用较少的数据库结构信息.这使人们更难理解数据库的底层结构并设计合适的攻击.
  3. 它们只接受参数,因此参数化查询的优点就在那里.当然 - IMO你仍然需要清理你的输入 - 特别是如果你在存储过程中使用动态SQL.

缺点是 -

  1. 它们(存储过程)很难维护,并且很快就会繁殖.这使得管理它们成为一个问题.
  2. 它们不太适合动态查询 - 如果它们构建为接受动态代码作为参数,则许多优点被否定.


Manish Shriv.. 278

有许多方法可以防止SQL注入和其他SQL攻击.您可以在互联网上轻松找到它(谷歌搜索).当然PDO是一个很好的解决方案.但我想建议你从SQL注入一些良好的链接预防.

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

用于SQL注入的PHP手册

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

和其他一些像使用MySQL和PHP预防SQL注入

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

我想告诉您:为什么我们尝试使用以下简短示例来阻止SQL注入:

查询登录验证匹配:

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

现在,如果有人(黑客)放弃

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

和密码任何....

查询将被解析到系统中,最多只能:

$query="select * from users where email='admin@emali.com' OR '1=1';

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


RDK.. 254

我想如果有人想使用PHP和MySQL或其他一些dataBase服务器:

  1. 考虑学习PDO(PHP数据对象) - 它是一个数据库访问层,提供访问多个数据库的统一方法.
  2. 考虑学习MySQLi
  3. 使用本机PHP函数,如:strip_tags,mysql_real_escape_string或如果是变量数字,只需(int)$foo.在这里阅读有关PHP中变量类型的更多信息.如果您使用的是PDO或MySQLi等库,请始终使用PDO :: quote()mysqli_real_escape_string().

图书馆示例:

---- PDO

-----没有占位符 - 适合SQL注入!这不好

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

-----未命名的占位符

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

-----命名占位符

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

--- MySQLi

$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();

PS:

PDO轻松赢得这场战斗.通过支持12个不同的数据库驱动程序和命名参数,我们可以忽略小的性能损失,并习惯其API.从安全角度来看,只要开发人员以他们应该使用的方式使用它们,它们都是安全的

虽然PDO和MySQLi都非常快,但MySQLi在基准测试中的表现速度要快得多 - 非准备语句约为2.5%,准备语句约为6.5%.

请测试您的数据库的每个查询 - 这是防止注入的更好方法.


devOp.. 247

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

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

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

  • 这是我使用"转义值"而不是预备语句的少数情况之一.整数类型转换非常有效. (2认同)

nbari.. 223

如果您想利用RedisMemcached等缓存引擎,可能会选择DALMP.它使用纯MySQLi.检查:使用PHP的MySQL的DALMP数据库抽象层.

此外,您可以在准备查询之前"准备"您的参数,以便您可以构建动态查询,并在最后具有完全准备好的语句查询.使用PHP的MySQL的DALMP数据库抽象层.


Xeoncross.. 214

对于那些不确定如何使用PDO(来自mysql_函数)的人,我制作了一个非常非常简单的PDO包装器,它是一个单独的文件.它的存在表明,完成应用程序所需的所有常见事务是多么容易.适用于PostgreSQL,MySQL和SQLite.

基本上,在阅读本手册时阅读本文,了解如何在现实生活中使用PDO功能,以便以想要的格式存储和检索值.

我想要一个专栏

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

我想要一个数组(key => value)结果(即用于制作一个选择框)

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

我想要一行结果

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

我想要一系列结果

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


用户甲.. 212

使用这个PHP函数,mysql_escape_string()您可以快速获得良好的预防.

例如:

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

mysql_escape_string - 转义一个字符串以在mysql_query中使用

为了更多预防,您可以在最后添加...

wHERE 1=1   or  LIMIT 1

最后你得到:

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


Danijel.. 195

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

不要使用MySQL,不推荐使用此扩展,使用MySQLiPDO.

库MySQLi

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

例:

$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_preparemysqli_stmt_bind_param,其中必须提供相应绑定变量的类型以进行适当的转换:

例:

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

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

$stmt->execute();

无论您使用预处理语句还是mysqli_real_escape_string,您始终必须知道您正在使用的输入数据的类型.

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

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

例:

$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

  • 这个问题非常通用.上面有一些很好的答案,但大多数建议准备好的陈 MySQLi异步不支持预处理语句,因此sprintf看起来是这种情况的一个很好的选择. (3认同)

Apurv Nerlek.. 177

可以通过在数据库本身中授予适当的权限来解决此问题的简单替代方法.例如:如果您使用的是MySQL数据库,则通过终端或提供的UI进入数据库,只需按照以下命令:

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

这将限制用户仅受限于指定的查询.删除删除权限,因此永远不会从PHP页面触发的查询中删除数据.第二件事是刷新权限,以便MySQL刷新权限和更新.

FLUSH PRIVILEGES; 

有关冲洗的更多信息.

要查看用户的当前权限,请触发以下查询.

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

了解有关GRANT的更多信息.

  • 这个答案基本上是错误的**,因为它无助于防止注射,而只是试图减轻后果.徒然. (22认同)

Soumalya Ban.. 167

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

  1. 使用的mysql_real_escape_string(),这是一个预先定义的函数PHP,这代码添加反斜杠以下字符:\x00,\n,\r,\,',"\x1a.将输入值作为参数传递,以最大限度地减少SQL注入的可能性.
  2. 最先进的方法是使用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是最好的选择.

编辑:

mysql_real_escape_string()自PHP 5.5.0起不推荐使用.使用mysqli或PDO.

mysql_real_escape_string()的替代方法是

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

例:

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


用户甲.. 165

关于许多有用的答案,我希望在这个帖子中添加一些值.SQL注入是一种可以通过用户输入(由用户填充然后在查询中使用的输入)完成的攻击,SQL注入模式是正确的查询语法,而我们可以调用它:错误的查询由于不好的原因,我们假设可能存在是一个试图获取影响三个安全原则(机密性,完整性,可用性)的秘密信息(绕过访问控制)的坏人.

现在,我们的观点是防止SQL注入攻击等安全威胁,问题(如何使用PHP防止SQL注入攻击),更现实,数据过滤或清除输入数据是在使用用户输入数据时的情况查询,使用PHP或任何其他编程语言不是这种情况,或者更多人建议使用现代技术,如准备好的语句或当前支持SQL注入预防的任何其他工具,请考虑这些工具不再可用?您如何保护您的申请?

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

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

  1. SQL用户(限制用户权限):最常见的SQL操作是(SELECT,UPDATE,INSERT),那么,为什么要给不需要它的用户提供UPDATE权限?例如登录,搜索页面只使用SELECT,那么,为什么在这些具有高权限的页面中使用数据库用户呢? 规则:不要为所有权限创建一个数据库用户,对于所有SQL操作,您可以创建方案(deluser,selectuser,updateuser)作为用户名以便于使用.

最小特权原则

  1. 数据过滤:在构建任何查询之前,应验证和过滤用户输入,对于程序员来说,为每个用户输入变量定义一些属性非常重要: 数据类型,数据模式和数据长度.对于作为字符串(文本)的字段,必须使用精确规则精确验证(x和y)之间的数字字段:模式就是这种情况,例如,用户名必须只包含一些字符,我们可以说[a- zA-Z0-9_-.]长度在(x和n)之间变化,其中x和n(整数,x <= n). 规则:为我创建精确的过滤器和验证规则是最佳实践.

  2. 使用其他工具:在这里,我也同意你准备好的声明(参数化查询)和存储过程,这里的缺点是这些方法需要大多数用户不存在的高级技能,这里的基本思路是区分SQL查询和内部使用的数据,即使对于不安全的数据也可以使用这两种方法,因为这里的用户输入数据不会向原始查询添加任何内容,例如(any或x = x).有关更多信息,请阅读OWASP SQL注入预防备忘单.

现在,如果您是高级用户,请根据需要开始使用此防御,但是,对于初学者,如果他们无法快速实现存储过程并准备语句,最好尽可能多地过滤输入数据.

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

[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一旦检测到这些单词,就可以避免输入.

UPDATE1:

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

主要防御:

选项#1:准备语句的使用(参数化查询)
选项#2:使用存储过程
选项#3:转义所有用户提供的输入

附加防御:

还强制执行:最小权限
也执行:白名单输入验证

您可能知道,声称文章应该得到有效参数的支持,至少有一个参考!否则,它被视为攻击和不良声明!

UPDATE2:

从PHP手册,PHP:Prepared Statements - Manual:

转义和SQL注入

绑定变量将由服务器自动转义.服务器在执行之前将其转义值在适当的位置插入到语句模板中.必须向服务器提供绑定变量类型的提示,以创建适当的转换.有关更多信息,请参阅mysqli_stmt_bind_param()函数.

服务器内的值的自动转义有时被认为是防止SQL注入的安全功能.如果正确转义输入值,则可以使用非预处理语句实现相同程度的安全性.

UPDATE3:

我创建了一些测试用例,用于了解在使用预准备语句时PDO和MySQLi如何将查询发送到MySQL服务器:

PDO:

$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));

查询日志:

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

库MySQLi:

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

查询日志:

    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发送准备好的查询,两者都产生相同的结果

参考文献:

  1. SQL注入备忘单
  2. SQL注入
  3. 信息安全
  4. 安全原则
  5. 数据验证


Deepak Thoma.. 164

一种简单的方法是使用像CodeIgniterLaravel这样的PHP框架,它具有内置功能,如过滤和活动记录,这样您就不必担心这些细微差别.

  • 我认为问题的关键在于不使用这样的框架就可以完成. (7认同)

5ervant.. 138

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

如果攻击者试图通过PHP的$_GET变量或URL的查询字符串来攻击表单,那么如果他们不安全,你就能捕获它们.

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

因为1=1,2=2,1=2,2=1,1+1=2,等...都是攻击者的SQL数据库中的常见问题.也许许多黑客应用程序也使用它.

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


Chintan Gor.. 125

PHP和MySQL有很多答案,但这里有PHP和Oracle的代码,用于防止SQL注入以及经常使用oci8驱动程序:

$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);


Thomas Ahle.. 122

一个好主意是使用像Idiorm这样的"对象 - 关系映射器":

$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注入,还可以避免语法错误!还支持使用方法链接的模型集合,以便一次过滤或将操作应用于多个结果和多个连接.


Rakesh Sharm.. 119

使用PDOMYSQLi是防止SQL注入的一种很好的做法,但如果你真的想使用MySQL函数和查询,那么最好使用它

mysql_real_escape_string

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

有更多的能力可以防止这种情况:比如识别 - 如果输入是字符串,数字,字符或数组,那么有很多内置函数可以检测到这一点.此外,最好使用这些函数来检查输入数据.

IS_STRING

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

is_numeric

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

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

  • **警告!**`mysql_real_escape_string()`[不是绝对可靠](http://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string). (19认同)
  • 另外,使用is_string()检查$ _POST数组成员绝对没有意义 (9认同)
  • [`mysql_real_escape_string`](http://php.net/manual/en/function.mysql-real-escape-string.php)现已弃用,因此它不再是一个可行的选项.它将来会从PHP中删除.最好转到PHP或MySQL人推荐的内容. (7认同)
  • 主题:不要信任用户提交的数据.您期望的任何东西都是具有特殊字符或布尔逻辑的垃圾数据,它本身应该成为您可能正在执行的SQL查询的一部分.保留$ _POST值仅作为数据,而不是SQL部分. (2认同)

Calmarius.. 85

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

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#-String.Format中运行语句,如:

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

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

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


提问时间:

查看次数:

1533457 次

最近活跃:

5 月,3 周 前