如何防止PHP中的SQL注入?

How can I prevent SQL injection in PHP?

提问人: 提问时间:9/13/2008 最后编辑:44 revs, 36 users 14%Andrew G. Johnson 更新时间:7/17/2023 访问量:2163302

问:

这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或交互。

如果在未修改的情况下将用户输入插入到 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;--')

可以做些什么来防止这种情况发生?

php mysql sql 安全性 sql 注入

评论


答:

898赞 6 revs, 5 users 33%Kibbee #1

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

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

通过使用 PDO 而不是 、 和 函数,可以使应用程序从数据库中更加抽象,这在极少数情况下必须切换数据库提供程序。mysql_mysqli_pgsql_

评论

14赞 Your Common Sense 6/12/2020
这个答案具有误导性。PDO 不是一根魔杖,它仅通过存在来保护您的查询。您应该用占位符替换查询中的每个变量,以获得对 PDO 的保护。
0赞 Daniel L. VanDenBosch 3/5/2022
你有任何资源或进一步解释你的意思吗?当你说你指的是BindValue的东西吗?substitute every variable in your query with a placeholder
0赞 mckenzm 9/23/2022
@Daniel L. VanDenBosch,我们可以称这些主变量为主变量?大多数嵌入式 SQL 系统都这样称呼它们。如果它们不是占位符,则它们是常量,即使该值到达可以保存其他值的主机字段也是如此。最小化变量的数量提供了可预测的访问路径,但明显降低了可重用性。
487赞 12 revs, 11 users 37%Kiran Maniya #2

安全警告:此答案不符合安全最佳实践。转义不足以防止 SQL 注入,请改用预准备语句。使用下面概述的策略,风险自负。

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

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

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

评论

21赞 Polynomial 4/17/2013
如果你不引用字符串,它仍然是可注射的。举个例子。在这种情况下,由于缺少引号,设置为有效。也可以使用 和 将字符串注入到查询中。$q = "SELECT col FROM tbl WHERE x = $safe_var";$safe_var1 UNION SELECT password FROM usersCONCATCHR
31赞 eggyal 4/25/2014
警告! 不是万无一失的。mysql_real_escape_string()
13赞 jww 4/8/2015
mysql_real_escape_string 现已弃用,因此它不再是一个可行的选择。它将来会从 PHP 中删除。最好转到PHP或MySQL人员推荐的内容。
0赞 Abhijeet Kambli 2/15/2019
上面的代码不起作用。mysqli_real_escape_string需要两个参数。检查
1740赞 21 revs, 16 users 57%Matt Sheppard #3

若要使用参数化查询,需要使用 Mysqli 或 PDO。要使用 mysqli 重写您的示例,我们需要如下所示的内容。

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("server", "username", "password", "database_name");

$variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// "s" means the database expects a string
$stmt->bind_param("s", $variable);
$stmt->execute();

你要读的关键函数是mysqli::p repare

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

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

  • 如果要根据用户输入更改 SQL 的结构,则参数化查询将无济于事,并且 不涵盖所需的转义。在这种情况下,最好将用户的输入通过白名单传递,以确保只允许“安全”值通过。mysql_real_escape_string

评论

2赞 peiman F. 3/9/2018
使用就足够了,还是我也必须使用参数化?mysql_real_escape_string
10赞 Goufalite 3/9/2018
@peimanF。保持使用参数化查询的良好做法,即使在本地项目中也是如此。使用参数化查询,可以保证不会有 SQL 注入。但请记住,您应该清理数据以避免虚假检索(即 XSS 注入,例如将 HTML 代码放入文本中)htmlentities
3赞 Richard 4/5/2018
@peimanF。参数化查询和绑定值的良好做法,但真正的转义字符串目前很好
0赞 Steen Schütt 12/5/2018
我理解包含完整性,但不喜欢首先列出最容易出错的方法。读者可能会很快抓住第一个例子。好在它现在已被弃用:)mysql_real_escape_string()
5赞 Rick James 12/1/2019
@SteenSchütt - 所有函数均已弃用。它们被类似的函数所取代,例如 .mysql_*mysqli_*mysqli_real_escape_string
9626赞 55 revs, 35 users 20%Your Common Sense #4

避免SQL注入攻击的正确方法,无论你使用哪个数据库,都是将数据与SQL分离,这样数据就保留在数据中,永远不会被SQL解析器解释为命令。可以使用格式正确的数据部分创建 SQL 语句,但如果您不完全了解详细信息,则应始终使用准备好的语句和参数化查询。这些是与任何参数分开发送到数据库服务器并由数据库服务器解析的 SQL 语句。这样,攻击者就不可能注入恶意 SQL。

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

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

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
    
  2. 使用 MySQLi(用于 MySQL):
    从 PHP 8.2+ 开始,我们可以利用 execute_query() 在一种方法中准备、绑定参数和执行 SQL 语句:

    $result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]);
     while ($row = $result->fetch_assoc()) {
         // Do something with $row
     }
    

    最高PHP8.1:

     $stmt = $db->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)。PDO是通用选项。pg_prepare()pg_execute()


正确设置连接

PDO的

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

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8mb4', 'user', 'password');

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

在上面的示例中,错误模式并不是绝对必要的,但建议添加它。这样,PDO将通过抛出.PDOException

但是,必须的是第一行,它告诉 PDO 禁用模拟的预准备语句并使用真正的预准备语句。这确保了在将语句和值发送到MySQL服务器之前,PHP不会对其进行解析(使可能的攻击者没有机会注入恶意SQL)。setAttribute()

虽然您可以在构造函数的选项中设置 ,但需要注意的是,“旧”版本的 PHP(5.3.6 之前)会静默地忽略 DSN 中的 charset 参数charset

Mysqli的

对于mysqli,我们必须遵循相同的例程:

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting
$dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
$dbConnection->set_charset('utf8mb4'); // charset

解释

传递到的 SQL 语句由数据库服务器解析和编译。通过指定参数(如上例所示的命名参数或命名参数),您可以告诉数据库引擎要筛选的位置。然后,当您调用 时,预准备语句将与您指定的参数值组合在一起。prepare?:nameexecute

这里重要的是参数值与编译的语句相结合,而不是 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([ 'column' => $unsafeValue ]);

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

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

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

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

评论

59赞 Randall Valenciano 1/20/2016
此外,mysql_query的官方文档只允许执行一个查询,因此除了 ;被忽略。即使这已经被弃用了,在 PHP 5.5.0 下也有很多系统可能会使用这个功能。php.net/manual/en/function.mysql-query.php
20赞 AbbasAli Hashemian 1/24/2016
这是一个坏习惯,但是一个后问题解决方案:不仅适用于 SQL 注入,而且适用于任何类型的注入(例如,F3 框架 v2 中有一个视图模板注入孔),如果您有一个现成的旧网站或应用程序正在遭受注入缺陷,一种解决方案是在引导时使用转义值重新分配 supperglobal 预定义变量的值,例如 $_POST。通过PDO,仍然可以逃脱(也适用于今天的框架):substr($pdo->quote($str, \PDO::P ARAM_STR), 1, -1)
26赞 donis 11/18/2016
这个答案缺乏对什么是预准备语句的解释 - 一件事 - 如果您在请求期间使用大量准备好的语句,它会对性能造成影响,有时它会造成 10 倍的性能影响。更好的情况是使用关闭参数绑定的 PDO,但关闭语句准备。
14赞 Kassem Itani 11/6/2017
使用PDO更好,如果您使用直接查询,请确保使用mysqli::escape_string
6赞 p0358 11/19/2018
@Alix这在理论上听起来是个好主意,但有时值需要不同类型的转义,例如对于 SQL 和 HTML
665赞 3 revs, 3 users 78%Imran #5

使用和准备的查询。PDO

($conn是一个对象)PDO

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();
396赞 2 revs, 2 users 67%Rob #6

无论你最终使用什么,确保你检查你的输入没有被其他善意的垃圾破坏,如果有必要,运行它或任何东西来消毒它。magic_quotesstripslashes

评论

13赞 Rob 4/25/2011
事实上;开着magic_quotes跑步只会鼓励不良练习。但是,有时您不能始终将环境控制到该级别 - 要么您无权管理服务器,要么您的应用程序必须与(不寒而栗)依赖于此类配置的应用程序共存。出于这些原因,编写可移植的应用程序是件好事 - 尽管如果您确实控制了部署环境,例如,因为它是一个内部应用程序,或者仅在您的特定环境中使用,那么显然这些努力是浪费的。
29赞 BryanH 1/17/2013
从 PHP 5.4 开始,被称为“魔法引号”的可憎之物已被杀死。并很好地摆脱了坏垃圾。
313赞 3 revs, 3 users 76%Nikhil #7

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

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

缺点是 -

  1. 它们(存储过程)很难维护,并且往往会很快成倍增加。这使得管理它们成为一个问题。
  2. 它们不太适合动态查询 - 如果它们被构建为接受动态代码作为参数,那么很多优点就会被否定。
517赞 7 revs, 7 users 60%rahularyansharma #8

已弃用的警告:这个答案的示例代码(就像问题的示例代码一样)使用了 PHP 的扩展,该扩展在 PHP 5.5.0 中被弃用,并在 PHP 7.0.0 中被完全删除。MySQL

安全警告:此答案不符合安全最佳实践。转义不足以防止 SQL 注入,请改用预准备语句。使用下面概述的策略,风险自负。(此外,在 PHP 7 中被删除。mysql_real_escape_string()

重要

防止 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 注入防护中找到更多详细信息。

评论

33赞 Álvaro González 2/26/2013
这是您可以使用旧版 mysql 扩展做的最好的事情。对于新代码,建议切换到 mysqli 或 PDO。
7赞 sectus 7/9/2013
我不同意这种“防止这些攻击的特制功能”。我认为目的是允许为每个输入数据字符串构建正确的SQL查询。预防 sql-injection 是此功能的副作用。mysql_real_escape_string
5赞 Nazca 3/13/2014
您不使用函数来编写正确的输入数据字符串。你只需要写出不需要转义或已经转义的正确内容。mysql_real_escape_string() 可能在设计时考虑到了您提到的目的,但它的唯一价值是防止注入。
22赞 eggyal 4/25/2014
警告! 不是万无一失的。mysql_real_escape_string()
10赞 jww 4/8/2015
mysql_real_escape_string 现已弃用,因此它不再是一个可行的选择。它将来会从 PHP 中删除。最好转到PHP或MySQL人员推荐的内容。
377赞 5 revs, 4 users 77%Cedric #9

已弃用的警告:这个答案的示例代码(就像问题的示例代码一样)使用了 PHP 的扩展,该扩展在 PHP 5.5.0 中被弃用,并在 PHP 7.0.0 中被完全删除。MySQL

安全警告:此答案不符合安全最佳实践。转义不足以防止 SQL 注入,请改用预准备语句。使用下面概述的策略,风险自负。(此外,在 PHP 7 中被删除。mysql_real_escape_string()

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

这些示例容易受到 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 注入(当转义不够时)

评论

3赞 Josip Ivic 9/15/2015
如果采用输入验证技术,在该技术中,用户输入根据一组定义的长度、类型和语法规则以及业务规则进行身份验证,则可以阻止 SQL 注入。
1172赞 10 revs, 6 users 67%Your Common Sense #10

这里的每个答案都只涵盖了问题的一部分。 事实上,我们可以动态地将四个不同的查询部分添加到SQL中:

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

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

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

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

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

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

为了简化这个过程,我编写了一个白名单辅助函数,该函数在一行中完成所有工作:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

还有另一种方法可以保护标识符 - 转义,但我更愿意坚持将白名单作为一种更强大、更明确的方法。然而,只要你有一个引用的标识符,你就可以对引用字符进行转义,以确保它的安全。例如,默认情况下,对于 mysql,您必须将引号字符加倍才能对其进行转义。对于其他 DBMS,转义规则会有所不同。

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

因此,一般性建议可以表述为

  • 任何表示 SQL 数据文本(或者,简单地说 - SQL 字符串或数字)的变量都必须通过预准备语句添加。没有例外。
  • 任何其他查询部分(如 SQL 关键字、表或字段名称或运算符)都必须通过白名单进行筛选。

更新

尽管对 SQL 注入保护的最佳实践达成了普遍共识,但仍然存在许多不良做法。其中一些已经深深地扎根于PHP用户的脑海中。例如,在这个页面上,有 80 多个已删除的答案(尽管大多数访问者看不到)——由于质量差或宣传不良和过时的做法,所有答案都被社区删除了。更糟糕的是,一些糟糕的答案并没有被删除,而是繁荣昌盛。

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

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

我认为这一切都是因为一个非常古老的迷信,得到了OWASPPHP手册等权威的支持,它宣称任何“逃逸”和保护免受SQL注入之间的平等。

不管PHP手册怎么说,*_escape_string绝不能使数据安全,也从来都不是故意的。除了对字符串以外的任何 SQL 部分都无用之外,手动转义是错误的,因为它是手动的,与自动相反。

而OWASP使情况变得更糟,强调逃避用户输入,这完全是无稽之谈:在注入保护的上下文中不应该有这样的词。每个变量都具有潜在的危险性 - 无论来源如何!或者,换句话说,每个变量都必须正确格式化才能放入查询中 - 无论来源如何。目的地才是最重要的。当开发人员开始将绵羊与山羊分开时(考虑某些特定变量是否“安全”),他/她就迈出了走向灾难的第一步。更不用说,即使是措辞也暗示在入口点进行批量转义,类似于非常神奇的引号功能——已经被鄙视、弃用和删除。

因此,与任何“转义”不同,预准备语句确实是防止 SQL 注入(如果适用)的措施。

评论

0赞 mickmackusa 9/22/2023
array_search()可以返回一个整数(包括 ),或者 -- 应该重新审视这个答案的这一方面。0false
270赞 3 revs, 3 users 86%devOp #11

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

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

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

评论

3赞 HoldOffHunger 3/14/2016
这是我使用“转义值”而不是预处理语句的少数情况之一。而且整数类型的转换非常有效。
322赞 Johannes Fahrenkrug #12

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

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

评论

9赞 Anthony Rutledge 1/5/2017
我认为你的第一段很重要。理解是关键。此外,每个人都不是在为公司工作。对于很多人来说,框架实际上与理解的想法背道而驰。在截止日期前工作时,熟悉基本面可能不被重视,但自己动手的人喜欢亲自动手。框架开发人员没有那么特权,以至于其他人都必须鞠躬并假设他们永远不会犯错误。做出决定的权力仍然很重要。谁能说我的框架将来不会取代其他方案?
0赞 Johannes Fahrenkrug 1/5/2017
@AnthonyRutledge 你是绝对正确的。了解正在发生的事情以及原因非常重要。然而,一个真正尝试过的、积极使用和开发的框架遇到并解决了很多问题,并修补了很多安全漏洞的可能性非常高。最好查看源代码以了解代码质量。如果它是未经测试的烂摊子,它可能不安全。
3赞 Anthony Rutledge 1/5/2017
这里。这里。好点。但是,您是否同意许多人可以学习和学习采用 MVC 系统,但并不是每个人都可以手动复制它(控制器和服务器)。在这一点上,人们可能走得太远了。在加热我女朋友给我做的花生酱山核桃饼干之前,我需要了解我的微波炉吗?;-)
3赞 Johannes Fahrenkrug 1/5/2017
@AnthonyRutledge我同意!我认为用例也会有所不同:我是为我的个人主页构建一个照片库,还是构建一个网上银行网络应用程序?在后一种情况下,了解安全性的细节以及我使用的框架如何解决这些问题非常重要。
3赞 Anthony Rutledge 1/5/2017
啊,安全例外是自己动手的必然结果。看,我倾向于愿意冒着一切风险去破产。:-)开玩笑。只要有足够的时间,人们就可以学会制作一个非常安全的应用程序。太多人匆匆忙忙。他们举起双手,认为这些框架更安全。毕竟,他们没有足够的时间来测试和解决问题。此外,安全是一个需要专门研究的领域。它不是仅仅通过理解算法和设计模式而深入了解的程序员。
314赞 7 revs, 5 users 56%Manish Shrivastava #13

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

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

PHPSQL注入手册

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

还有一些其他的,比如用MySQL和PHP防止SQL注入

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

我想让你知道:为什么我们试图通过下面的简短示例来阻止SQL注入:

查询登录身份验证匹配项:

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

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

$_POST['email']= [email protected]' OR '1=1

并输入任何密码......

查询将仅解析到系统中,最多:

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

另一部分将被丢弃。那么,会发生什么?未经授权的用户(黑客)将能够在没有密码的情况下以管理员身份登录。现在,他/她可以执行管理员/电子邮件人员可以执行的任何操作。看,如果不阻止SQL注入,这是非常危险的。

230赞 5 revs, 5 users 75%Nicolas Finelli #14

安全警告:此答案不符合安全最佳实践。转义不足以防止 SQL 注入,请改用预准备语句。使用下面概述的策略,风险自负。(此外,在 PHP 7 中被删除。mysql_real_escape_string()

警告:此时删除了 mysql 扩展。我们建议使用 PDO 扩展

使用此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
180赞 6 revs, 4 users 73%Soumalya Banerjee #15

安全警告:此答案不符合安全最佳实践。转义不足以防止 SQL 注入,请改用预准备语句。使用下面概述的策略,风险自负。(此外,在 PHP 7 中被删除。mysql_real_escape_string()

已弃用警告:mysql 扩展目前已弃用。我们建议使用 PDO 扩展

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

  1. 使用 ,这是 PHP 中的预定义函数,此代码将反斜杠添加到以下字符:、、、、和 。将输入值作为参数传递,以最大程度地减少 SQL 注入的可能性。mysql_real_escape_string()\x00\n\r\'"\x1a
  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");
236赞 4 revs, 4 users 81%Xeoncross #16

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

基本上,在阅读手册的同时阅读它,了解如何将 PDO 函数用于现实生活,以便以所需的格式轻松存储和检索值。

我想要一列

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

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

$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'));
587赞 9 revs, 7 users 73%Zaffy #17

正如你所看到的,人们建议你最多使用准备好的语句。这没有错,但是如果每个进程只执行一次查询,则性能会略有下降。

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

我的方法:

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

  • 如果你期望从整数中得到任何其他东西,就十六进制它。如果你十六进制它,你将完美地转义所有输入。在 C/C++ 中有一个叫做 mysql_hex_string() 的函数,在 PHP 中你可以使用 bin2hex()。

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

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

因此,例如,查询:

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 前缀的区别

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

前缀只能用于数据列,如 、 、 、 等。 此外,如果您要插入一个空字符串,它的使用会有点复杂。
您必须将其完全替换为 ,否则会出现错误。
0xcharvarchartextblockbinary''

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


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

请注意,这种十六进制方法通常用作 SQL 注入攻击,其中整数就像字符串一样,并且仅使用 .然后,您可以避免使用引号。mysql_real_escape_string

例如,如果你只是做这样的事情:

"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');

评论

0赞 Sumit Gupta 6/1/2013
@Zaffy,我喜欢这个想法,但是性能呢,我的意思是,如果您有 100 万条记录和 1000 个用户在搜索,与准备解决方案相比,速度会变慢吗?
0赞 Sumit Gupta 6/1/2013
我只是测试 SELECT * FROM where product_code LIKE ( '%42%') 确实可以找到记录,但 SELECT * FROM where product_code LIKE ('%' +0x3432 +'%') 没有,所以它根本不起作用还是我做错了什么?tblproductstblproducts
9赞 Zaffy 6/2/2013
@SumitGupta是的,你做到了。MySQL不与但连接。至于性能:我不认为这会影响性能,因为 mysql 必须解析数据,并且 origin 是字符串还是十六进制都无关紧要+CONCAT
1赞 Your Common Sense 6/12/2020
这种过于复杂的方法绝对是徒劳的。人们可以使用简单的引用功能来代替这种 hexing/unhexing 用具。但它同样受到限制,使你的应用程序在不适用的情况下暴露于 SQL 注入。"'".$mysqli->escape_string($_GET["id"])."'"
1赞 zokk7 1/28/2021
@Zaffy,谢谢,它有很大帮助。我自己测试过,您的“公式”十六进制/非十六进制可以防止最常见的 SQL 注入攻击。有可能打破了这个,过程泄漏还是什么?至少在某种程度上,你知道.
279赞 6 revs, 4 users 51%RDK #18

我相信,如果有人打算将PHP与MySQL或任何其他数据库服务器一起使用:

  1. 考虑探索 PDO(PHP 数据对象) - 一个非常宝贵的数据库访问层,它为访问各种数据库提供了一致的方法。
  2. 考虑深入研究 MySQLi,这是学习和利用 MySQL 数据库的绝佳选择。

附言

PDO 具有支持 12 种不同的数据库驱动程序和命名参数等优点。从安全的角度来看,只要开发人员以应有的方式使用它们,它们都是安全的

评论

1赞 mickmackusa 5/29/2020
MySQLI不正确。第一个参数表示数据类型。
186赞 8 revs, 8 users 48%Apurv Nerlekar #19

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

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

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

FLUSH PRIVILEGES; 

有关 FLUSH 的更多信息。

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

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

了解有关 GRANT 的更多信息。

评论

27赞 Your Common Sense 5/20/2016
这个答案本质上是错误的,因为它无助于防止注射预防,而只是试图减轻后果。枉。
1赞 Apurv Nerlekar 5/26/2016
是的,它没有提供解决方案,但您可以事先做些什么来避免事情。
3赞 Alexander Holsgrove 10/15/2016
@Apurv 你不想“软化后果”,你想尽一切可能防止它。公平地说,设置正确的用户访问权限很重要,但并不是 OP 真正要求的。
182赞 18 revs, 8 users 53%user1646111 #20

关于许多有用的答案,我希望为这个线程增加一些价值。

SQL注入是一种攻击,可以通过用户输入(由用户填充然后在查询中使用的输入)来完成。SQL注入模式是正确的查询语法,而我们可以称之为:出于不良原因的不良查询,我们假设可能有一个坏人试图获取影响安全性(机密性、完整性和可用性)三个原则的秘密信息(绕过访问控制)。

现在,我们的观点是防止诸如SQL注入攻击之类的安全威胁,问题问(如何使用PHP防止SQL注入攻击),更现实地说,数据过滤或清除输入数据是在此类查询中使用用户输入数据时的情况,使用PHP或任何其他编程语言的情况是不一样的,或者像更多人推荐的那样使用现代技术,例如准备语句或任何其他工具,目前支持SQL注入防护,认为这些工具已经不可用了吗?如何保护应用程序?

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

数据过滤(将不安全数据转换为安全数据)

考虑到 PDOMySQLi 不可用。如何保护应用程序?你强迫我使用它们吗?除了PHP之外,其他语言呢?我更喜欢提供一般的想法,因为它可以用于更广泛的边界,而不仅仅是特定的语言。

  1. SQL 用户(限制用户权限):最常见的 SQL 操作是(SELECT、UPDATE、INSERT),那么,为什么要将 UPDATE 权限授予不需要它的用户呢?例如,登录和搜索页面只使用SELECT,那么,为什么在这些页面中使用具有高权限的DB用户呢?

规则:不要为所有权限创建一个数据库用户。对于所有 SQL 操作,您可以创建您的方案,例如 (deluser, selectuser, updateuser) 作为用户名,以便于使用。

请参阅最小特权原则

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

  2. 使用其他工具:在这里,我也同意你的观点,即准备语句(参数化查询)和存储过程。这里的缺点是这些方法需要高级技能,而大多数用户并不存在这些技能。这里的基本思想是区分 SQL 查询和内部使用的数据。即使使用不安全的数据,也可以使用这两种方法,因为此处的用户输入数据不会向原始查询添加任何内容,例如 (any 或 x=x)。

有关更多信息,请阅读 OWASP SQL Injection Prevention 备忘单

现在,如果您是高级用户,请随心所欲地开始使用此防御措施,但是,对于初学者来说,如果他们无法快速实现存储过程并准备语句,则最好尽可能多地筛选输入数据。

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

[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 提供的内容:

主要防御措施: 选项 #1:使用预准备语句(参数化查询)
选项 #2:使用存储过程
选项 #3:转义所有用户提供的输入 其他防御措施: 同时强制执行:最低权限
同时执行:



白名单输入

验证

如您所知,声称一篇文章应该得到有效论据的支持,至少应该有一个参考文献!否则,它被认为是一种攻击和糟糕的索赔!

更新2:

摘自 PHP 手册,PHP:预准备语句 - 手册:

转义和 SQL 注入

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

服务器内值的自动转义有时是 被认为是防止 SQL 注入的安全功能。一样 在以下情况下,可以使用未准备好的语句实现安全程度 输入值已正确转义。

更新3:

我创建了测试用例,用于了解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

很明显,准备好的语句也是在逃避数据,仅此而已。

如上所述,

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

因此,这证明在发送任何查询之前对整数值进行数据验证(例如)是一个好主意。此外,在发送查询之前防止恶意用户数据是一种正确有效的方法intval()

有关更多详细信息,请参阅此问题:PDO向MySQL发送原始查询,而Mysqli发送准备好的查询,两者产生相同的结果

引用:

  1. SQL注入备忘单
  2. SQL注入
  3. 信息安全
  4. 安全原则
  5. 数据验证
152赞 7 revs, 4 users 53%5ervant #21

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

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

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

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

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

231赞 7 revs, 5 users 77%Danijel #22

安全警告:此答案不符合安全最佳实践。转义不足以防止 SQL 注入,请改用预准备语句

在 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

顾名思义,使用 is 用于转义字符串中的特殊字符,因此它不会使整数安全。此函数的目的是防止破坏 SQL 语句中的字符串,以及它可能对数据库造成的损害。 使用得当时是一个有用的功能,尤其是与 结合使用时。mysqli_real_escape_stringmysqli_real_escape_stringsprintf

例:

$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

评论

3赞 Dustin Graham 4/24/2016
这个问题非常笼统。上面有一些很好的答案,但大多数建议准备好的陈述。MySQLi 异步不支持预准备语句,因此在这种情况下,sprintf 看起来是一个不错的选择。
0赞 Sybille Peters 7/13/2023
链接“转义不足以防止SQL注入”当前已损坏(502)。可能是暂时的,但不会激发对网站的信心:paragonie.com/blog/2015/05/......
171赞 5 revs, 4 users 29%Deepak Thomas #23

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

评论

11赞 Sanke 1/3/2018
我认为问题的全部意义在于在不使用这样的框架的情况下完成这项工作。
126赞 5 revs, 4 users 74%Rakesh Sharma #24

这篇文章被标记为过时,因为内容已过时。它目前不接受新的交互。

已弃用的警告:这个答案的示例代码(就像问题的示例代码一样)使用了 PHP 的扩展,该扩展在 PHP 5.5.0 中被弃用,并在 PHP 7.0.0 中被完全删除。MySQL

安全警告:此答案不符合安全最佳实践。转义不足以防止 SQL 注入,请改用预准备语句。使用下面概述的策略,风险自负。(此外,在 PHP 7 中被删除。mysql_real_escape_string()

使用 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

评论

10赞 Your Common Sense 1/18/2014
此外,使用 is_string() 检查 $_POST 数组成员绝对没有意义
21赞 eggyal 4/25/2014
警告! 不是万无一失的。mysql_real_escape_string()
10赞 jww 4/8/2015
mysql_real_escape_string 现已弃用,因此它不再是一个可行的选择。它将来会从 PHP 中删除。最好转到PHP或MySQL人员推荐的内容。
131赞 4 revs, 4 users 56%Chintan Gor #25

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);
90赞 4 revs, 2 users 99%Calmarius #26

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

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

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

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

安全更新:以前的版本允许通过将 {#} 个令牌添加到用户数据中来注入。如果替换包含这些令牌,则此版本不会导致问题。str_replacepreg_replace_callback

136赞 5 revs, 4 users 78%Thomas Ahle #27

一个好主意是使用像 Idorm 这样的对象关系映射器

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

评论

0赞 pocketrocket 10/9/2020
老实说,我不同意你的建议。这可能会导致任何 ORM 都产生一种虚假的安全感。当然,其中大多数都负责准备语句和参数化查询。来到这篇文章的新手可能仍然会感到安全,因为选择任何 ORM - 信任它们。一般来说,ORM通过隐藏/抽象实现细节来简化事情。你真的想检查(或盲目相信)它是如何完成的。经验法则:它背后的开源社区(支持)越大,它就越不容易被搞砸;)
1赞 Shayne 10/28/2020
老实说,这不是最糟糕的主意,pocketrocket。根据 ORM 的不同,ORM 的作者很有可能比编码人员更了解 SQL。这有点像加密的旧规则,除非你在该领域的研究论文上有你的名字,否则不要滚动你自己的名字,因为攻击者很可能在该领域的论文上有他的名字。也就是说,如果它是一个 ORM,需要你提供全部或部分查询(即 Model.filter('where foo = ?',bar),那么你最好滚动 SQL