为什么不应该使用的技术原因是什么 mysql_* 功能? (例如。 mysql_query(), mysql_connect() 要么 mysql_real_escape_string())?
即使他们在我的网站上工作,为什么还要使用别的东西?
如果他们不在我的网站上工作,为什么我会收到错误
警告:mysql_connect():没有这样的文件或目录
为什么不应该使用的技术原因是什么 mysql_* 功能? (例如。 mysql_query(), mysql_connect() 要么 mysql_real_escape_string())?
即使他们在我的网站上工作,为什么还要使用别的东西?
如果他们不在我的网站上工作,为什么我会收到错误
警告:mysql_connect():没有这样的文件或目录
MySQL扩展:
由于它已被弃用,因此使用它会使您的代码不再适用于未来。
缺乏对预准备语句的支持尤其重要,因为它们提供了一种更清晰,更不易出错的转义和引用外部数据的方法,而不是通过单独的函数调用手动转义它。
看到 SQL扩展的比较。
PHP提供了三种不同的API来连接MySQL。这些是 mysql(从PHP 7开始删除), mysqli,和 PDO 扩展。
该 mysql_* 以前的功能很受欢迎,但不再鼓励它们的使用。文档团队正在讨论数据库安全情况,并教育用户远离常用的ext / mysql扩展是其中的一部分(检查 php.internals:弃用ext / mysql)。
后来的PHP开发团队已经决定生成 E_DEPRECATED 用户连接到MySQL时的错误,无论是通过 mysql_connect(), mysql_pconnect() 或内置的隐式连接功能 ext/mysql。
ext/mysql 是 自PHP 5.5起正式弃用 并且一直 从PHP 7开始删除。
看红盒子?
当你继续下去 mysql_* 功能手册页,你看到一个红色的框,解释它不应该再使用。
远离 ext/mysql 不仅涉及安全性,还涉及访问MySQL数据库的所有功能。
ext/mysql 是为...而建的 MySQL 3.23 从那以后只有很少的添加,同时大部分保持与这个旧版本的兼容性,这使代码有点难以维护。缺少不受支持的功能 ext/mysql 包括:(来自PHP手册)。
不使用的理由 mysql_* 功能:
缺乏对预准备语句的支持特别重要,因为它们提供了一种更清晰,更不容易出错的方法来转义和引用外部数据,而不是通过单独的函数调用来手动转义它。
见 SQL扩展的比较。
抑制弃用警告
代码正在转换为 MySQLi/PDO, E_DEPRECATED 可以通过设置来抑制错误 error_reporting 在 php.ini中 排除 E_DEPRECATED:
error_reporting = E_ALL ^ E_DEPRECATED
请注意,这也将隐藏 其他弃用警告但是,它可能用于除MySQL之外的其他东西。 (来自PHP手册)
这篇文章 PDO与MySQLi:你应该使用哪个? 通过 德扬马里亚诺维奇 将帮助您选择。
更好的方法是 PDO,我现在写的很简单 PDO 教程。
一个。 ”PDO - PHP数据对象 - 是一个数据库访问层,提供访问多个数据库的统一方法。“
同 mysql_* 函数或我们可以用旧的方式说(在PHP 5.5及以上版本中弃用)
$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);
同 PDO:您需要做的就是创建一个新的 PDO 目的。构造函数接受用于指定数据库源的参数 PDO的构造函数主要采用四个参数 DSN (数据源名称)和可选 username, password。
在这里,我认为你熟悉除了 DSN;这是新的 PDO。一个 DSN 基本上是一串选项告诉我们 PDO 使用哪个驱动程序,以及连接详细信息。如需进一步参考,请检查 PDO MySQL DSN。
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');
注意: 你也可以用 charset=UTF-8,但有时它会导致错误,所以最好使用它 utf8。
如果有任何连接错误,它将抛出一个 PDOException 可以捕获的对象 Exception 进一步。
好读: 连接和连接管理¶
您还可以将多个驱动程序选项作为数组传递给第四个参数。我建议传递put的参数 PDO 进入异常模式。因为有的 PDO 驱动程序不支持本机预处理语句,所以 PDO 执行准备的模拟。它还允许您手动启用此仿真。要使用本机服务器端预处理语句,您应该显式设置它 false。
另一种是关闭在中启用的准备仿真 MySQL驱动程序默认情况下,但准备仿真应该关闭使用 PDO 安全。
我稍后会解释为什么应该关闭准备仿真。要查找原因,请检查 这个帖子。
它仅在您使用旧版本时才可用 MySQL 我不推荐。
以下是如何执行此操作的示例:
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8',
'username',
'password',
array(PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
我们可以在PDO构建后设置属性吗?
是,我们也可以在PDO构造之后设置一些属性 setAttribute 方法:
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8',
'username',
'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
错误处理更容易 PDO 比 mysql_*。
使用时的常见做法 mysql_* 是:
//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));
OR die() 因为我们无法处理错误,所以不是处理错误的好方法 die。它会突然结束脚本,然后将错误回显到您通常不希望向最终用户显示的屏幕,并让血腥的黑客发现您的架构。或者,返回值 mysql_* 函数通常可以与之结合使用 mysql_error() 处理错误。
PDO 提供更好的解决方案:例外。我们做的任何事情 PDO 应该包裹在一个 try - catch 块。我们可以强迫 PDO 通过设置错误模式属性进入三种错误模式之一。下面是三种错误处理模式。
PDO::ERRMODE_SILENT。它只是设置错误代码和行为几乎相同 mysql_* 你必须检查每个结果,然后看看 $db->errorInfo(); 获取错误详细信息。PDO::ERRMODE_WARNING 提高 E_WARNING。 (运行时警告(非致命错误)。脚本的执行不会停止。)PDO::ERRMODE_EXCEPTION:抛出异常。它表示PDO引发的错误。你不应该抛出一个 PDOException 来自你自己的代码。看到 例外 有关PHP中的异常的更多信息。它的行为非常像 or die(mysql_error());,当它没有被抓住。但不像 or die(), PDOException 如果您选择这样做,可以优雅地抓住并处理。好读:
喜欢:
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
你可以把它包起来 try - catch,如下所示:
try {
//Connect as appropriate as above
$db->query('hi'); //Invalid query!
}
catch (PDOException $ex) {
echo "An Error occured!"; //User friendly message/message you want to show to user
some_logging_function($ex->getMessage());
}
你不必处理 try - catch 马上。您可以随时抓住它,但我强烈建议您使用 try - catch。另外,在调用函数的函数外部捕获它可能更有意义 PDO 东东:
function data_fun($db) {
$stmt = $db->query("SELECT * FROM table");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
//Then later
try {
data_fun($db);
}
catch(PDOException $ex) {
//Here you can handle error and show message/perform action you want.
}
此外,你可以处理 or die() 或者我们可以这样说 mysql_*,但它会变得多种多样。您可以通过转动隐藏生产中的危险错误消息 display_errors off 并只是阅读你的错误日志。
现在,在阅读了上述所有内容之后,你可能会想:当我只是想开始简单的时候,那是什么 SELECT, INSERT, UPDATE, 要么 DELETE 声明?别担心,我们走了:

所以你在做什么 mysql_* 是:
<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());
$num_rows = mysql_num_rows($result);
while($row = mysql_fetch_assoc($result)) {
echo $row['field1'];
}
现在进来 PDO,你可以这样做:
<?php
$stmt = $db->query('SELECT * FROM table');
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['field1'];
}
要么
<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
//Use $results
注意:如果您使用如下方法(query()),此方法返回一个 PDOStatement 目的。因此,如果您想获取结果,请像上面一样使用它。
<?php
foreach($db->query('SELECT * FROM table') as $row) {
echo $row['field1'];
}
在PDO数据中,它是通过 ->fetch(),一种语句句柄的方法。在调用fetch之前,最好的方法是告诉PDO你想要获取数据的方式。在下面的部分我将解释这一点。
注意使用 PDO::FETCH_ASSOC 在里面 fetch() 和 fetchAll() 上面的代码。这说明 PDO 将行作为关联数组返回,并将字段名称作为键。还有许多其他的获取模式,我将逐一解释。
首先,我解释如何选择获取模式:
$stmt->fetch(PDO::FETCH_ASSOC)
在上面,我一直在使用 fetch()。您还可以使用:
PDOStatement::fetchAll() - 返回包含所有结果集行的数组PDOStatement::fetchColumn() - 从结果集的下一行返回单个列PDOStatement::fetchObject() - 获取下一行并将其作为对象返回。PDOStatement::setFetchMode() - 设置此语句的默认提取模式现在我来获取模式:
PDO::FETCH_ASSOC:返回由结果集中返回的列名索引的数组PDO::FETCH_BOTH (默认值):返回由结果集中返回的列名和0索引列号索引的数组还有更多的选择!全部了解它们 PDOStatement 获取文档。。
获取行数:
而不是使用 mysql_num_rows 要获得返回的行数,可以得到一个 PDOStatement 并做 rowCount(), 喜欢:
<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';
获取上次插入的ID
<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

我们在做什么 mysql_* 功能是:
<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);
在pdo中,同样的事情可以通过以下方式完成:
<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;
在上面的查询中 PDO::exec 执行SQL语句并返回受影响的行数。
稍后将介绍插入和删除。
只有在查询中不使用变量时,上述方法才有用。但是当你需要在查询中使用变量时,不要尝试像上面那样 准备好的声明或参数化声明 是。
Q. 什么是准备好的声明,为什么我需要它们?
一个。 预准备语句是预编译的SQL语句,可以通过仅将数据发送到服务器来多次执行。
使用预准备语句的典型工作流程如下(引自维基百科的三点3分):
准备:语句模板由应用程序创建并发送到数据库管理系统(DBMS)。某些值未指定,称为参数,占位符或绑定变量(标记为 ? 下面):
INSERT INTO PRODUCT (name, price) VALUES (?, ?)
DBMS在语句模板上解析,编译和执行查询优化,并存储结果而不执行它。
1.00对于第二个参数。您可以通过在SQL中包含占位符来使用预准备语句。基本上有三个没有占位符(不要尝试使用上面的变量),一个带有未命名的占位符,另一个带有命名的占位符。
Q. 那么现在,什么是命名占位符以及如何使用它们?
一个。 命名占位符。使用以冒号开头的描述性名称,而不是问号。我们不关心名称持有人的价值位置/顺序:
$stmt->bindParam(':bla', $bla);
bindParam(parameter,variable,data_type,length,driver_options)
您也可以使用执行数组进行绑定:
<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
另一个不错的功能 OOP 朋友是命名占位符能够将对象直接插入到数据库中,假设属性与命名字段匹配。例如:
class person {
public $name;
public $add;
function __construct($a,$b) {
$this->name = $a;
$this->add = $b;
}
}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);
Q. 那么现在,什么是未命名的占位符以及如何使用它们?
一个。 我们举个例子:
<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();
和
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));
在上面,你可以看到那些 ? 而不是像名字持有人那样的名字。现在在第一个例子中,我们将变量分配给各个占位符($stmt->bindValue(1, $name, PDO::PARAM_STR);)。然后,我们为这些占位符分配值并执行该语句。在第二个例子中,第一个数组元素转到第一个 ? 第二到第二 ?。
注意:在 未命名的占位符 我们必须处理我们传递给数组的数组中元素的正确顺序 PDOStatement::execute() 方法。
SELECT, INSERT, UPDATE, DELETE 准备好的查询SELECT:
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
INSERT:
$stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
$stmt->execute(array(':field1' => $field1, ':field2' => $field2));
$affected_rows = $stmt->rowCount();
DELETE:
$stmt = $db->prepare("DELETE FROM table WHERE id=:id");
$stmt->bindValue(':id', $id, PDO::PARAM_STR);
$stmt->execute();
$affected_rows = $stmt->rowCount();
UPDATE:
$stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
$stmt->execute(array($name, $id));
$affected_rows = $stmt->rowCount();
然而 PDO 和/或 MySQLi 不完全安全。检查答案 PDO准备好的语句是否足以阻止SQL注入? 通过 ircmaxell。另外,我引用他的回答中的一部分:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));
首先,让我们从我们给大家的标准评论开始:
请不要使用
mysql_*新代码中的函数。他们不再维护 并且正式弃用。见 红色的盒子?学习关于 准备好的陈述 相反,并使用 PDO 要么 库MySQLi - 本文 将帮助您决定哪个。如果你选择PDO, 这是一个很好的教程。
让我们逐句逐句解释:
它们不再维护,并且已被正式弃用
这意味着PHP社区正在逐渐放弃对这些非常旧的功能的支持。它们很可能不存在于PHP的未来(最新)版本中!继续使用这些函数可能会破坏(不是那么)远期的代码。
新! - 现在是ext / mysql 自PHP 5.5起正式弃用!
相反,你应该学习准备好的陈述
mysql_* 扩展不支持 准备好的陈述,这是(除其他外)一个非常有效的对策 SQL注入。它修复了MySQL依赖应用程序中的一个非常严重的漏洞,它允许攻击者获得对脚本的访问权限并执行 任何可能的查询 在您的数据库上。
有关更多信息,请参阅 如何在PHP中阻止SQL注入?
看红盒子?
当你去任何 mysql 功能手册页,你看到一个红色的框,解释它不应该再使用。
使用PDO或MySQLi
有更好,更强大和精心打造的替代品, PDO - PHP数据库对象,它提供了完整的OOP方法来进行数据库交互,以及 库MySQLi,这是MySQL特定的改进。
已经提到了分析和综合原因。对于新手来说,停止使用过时的mysql_函数是一个更重要的动机。
当代数据库API就是 更轻松 使用。
这主要是 绑定参数 这可以简化代码。与 优秀的教程(如上所示) 过渡到 PDO 并不过分艰苦。
一次重写更大的代码库需要时间。 Raison d'être为这个中间选择:
运用 <pdo_mysql.php> 你可以用旧的mysql_函数切换 最小的努力。它补充道 pdo_ 功能包装器替换他们的 mysql_ 同行。
只是 include_once("pdo_mysql.php"); 在每个必须与数据库交互的调用脚本中。
去除 功能前缀 到处 并替换它 mysql_pdo_。
mysql_connect() 变 pdo_connect()mysql_query() 变 pdo_query()mysql_num_rows() 变 pdo_num_rows()mysql_insert_id() 变 pdo_insert_id()mysql_fetch_array() 变 pdo_fetch_array()mysql_fetch_assoc() 变 pdo_fetch_assoc()mysql_real_escape_string() 变 pdo_real_escape_string()您的代码将起作用,但大多数看起来仍然相同:
include_once("pdo_mysql.php");
pdo_connect("localhost", "usrABC", "pw1234567");
pdo_select_db("test");
$result = pdo_query("SELECT title, html FROM pages");
while ($row = pdo_fetch_assoc($result)) {
print "$row[title] - $row[html]";
}
Etvoilà。
你的代码是 运用 PDO。
现在是时候了 利用 它。

你只需要一个不那么笨拙的API。
pdo_query() 为绑定参数添加了非常简单的支持。转换旧代码很简单:

将变量移出SQL字符串。
pdo_query()。? 作为变量之前的占位符。' 以前包含字符串值/变量的单引号。对于更长的代码,优势变得更加明显。
通常,字符串变量不仅仅插入到SQL中,而是与之间的转义调用连接起来。
pdo_query("SELECT id, links, html, title, user, date FROM articles
WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
pdo_real_escape_string($title) . "' AND user <> '" .
pdo_real_escape_string($root) . "' ORDER BY date")
同 ? 应用占位符你不必为此烦恼:
pdo_query("SELECT id, links, html, title, user, date FROM articles
WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)
请记住,pdo_ *仍然允许 两者任一。
只是不要逃避变量 和 在同一个查询中绑定它。
:named 占位符列表稍后。更重要的是,您可以在任何查询后安全地传递$ _REQUEST []变量。提交时 <form> 字段与数据库结构完全匹配,甚至更短:
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
这么简单。但是,让我们回过头来重新提一下建议和技术原因,了解你为什么要摆脱它们 和逃避。mysql_
sanitize() 功能一旦你转换了全部 打电话给 mysql_pdo_query 绑定params,删除所有冗余 pdo_real_escape_string 调用。
特别是你应该解决任何问题 sanitize 要么 clean 要么 filterThis 要么 clean_data 功能由日期教程以一种形式或另一种形式宣传:
function sanitize($str) {
return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}
这里最明显的错误是缺乏文档。更重要的是,过滤顺序完全错误。
正确的顺序是:弃用 stripslashes 那么,作为最里面的呼唤 trim之后 strip_tags, htmlentities 对于输出上下文,最后只有 _escape_string 因为它的应用程序应该直接在SQL intersparsing之前。
但作为第一步 摆脱了 _real_escape_string 呼叫。
您可能需要保留其余的 sanitize() 如果您的数据库和应用程序流期望HTML上下文安全的字符串,则现在运行。添加一条注释,它仅适用于今后的HTML转义。
字符串/值处理委托给PDO及其参数化语句。
如果有任何提及 stripslashes() 在您的清理功能中,它可能表示更高级别的疏忽。
这通常用于撤消已弃用的损坏(双重转义) magic_quotes。然而,这是 最好的固定中心,而不是逐字符串。
使用其中之一 用户空间逆转 方法。然后删除 stripslashes() 在里面 sanitize 功能。
关于magic_quotes的历史记录。 该功能已被正确弃用。它经常被错误地描述为失败 安全 然而,功能。但是magic_quotes也是一个失败的安全功能,因为网球作为营养来源失败了。这根本不是他们的目的。
PHP2 / FI中的原始实现仅使用“引号将自动转义,从而更容易将表单数据直接传递给msql查询“。值得注意的是,使用它是非常安全的 mSQL的,因为只支持ASCII。
然后PHP3 / Zend为MySQL重新引入了magic_quotes并错误地记录了它。但最初它只是一个 便利功能,不打算保密。
当您将字符串变量加密到SQL查询中时,它不会让您更加复杂。 MySQL再次分离代码和数据也是一项无关紧要的工作。

SQL注入只是在什么时候 数据流入代码 上下文。数据库服务器以后不能发现PHP最初在查询子句之间粘合变量的位置。
使用绑定参数,可以在PHP代码中分隔SQL代码和SQL上下文值。但它不会在幕后再次混乱(除了PDO :: EMULATE_PREPARES)。您的数据库接收未变量的SQL命令和1:1变量值。

虽然这个答案强调你应该关心掉落的可读性优势 。由于这种可见和技术数据/代码分离,偶尔也会有性能优势(重复INSERT只有不同的值)。 mysql_
请注意参数绑定仍然不是一个神奇的一站式解决方案 所有 SQL注入。它处理数据/值的最常见用途。但是不能将列名称/表标识符列入白名单,有助于动态子句构造,或者只是简单的数组值列表。
这些 pdo_* 包装函数构成了一个编码友好的止损API。 (这几乎是什么 MYSQLI 如果它不是特殊功能签名转移可能是。它们在大多数时候也暴露了真正的PDO。
重写不必停止使用新的pdo_函数名称。您可以逐个将每个pdo_query()转换为普通的$ pdo-> prepare() - > execute()调用。
不过,最好再次开始简化。例如,获取的常见结果:
$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {
可以只用foreach迭代替换:
foreach ($result as $row) {
或者更好的是直接和完整的数组检索:
$result->fetchAll();
在大多数情况下,您将获得比PDO或mysql_通常在查询失败后提供的更有用的警告。
所以这有希望看到一些 实际的 理由和价值下降的途径 。mysql_
只是切换到 PDO 并没有完全削减它。 pdo_query() 它也只是它的前端。
除非你还引入参数绑定或者可以利用更好的API中的其他内容,否则它是一个毫无意义的开关。我希望它的描述足够简单,不会进一步阻碍新人的沮丧。 (教育通常比禁止更好。)
虽然它有资格获得最简单的可能工作类别,但它仍然是非常实验性的代码。我只是在周末写的。然而,有许多替代品。只是google for PHP数据库抽象 并浏览一下。对于这样的任务,总会有很多优秀的库。
如果你想进一步简化你的数据库交互,那么mappers就像 巴黎/ Idiorm 值得一试。就像没有人再使用JavaScript中的平淡DOM一样,你现在不必保持原始的数据库接口。
该 mysql_ 功能:
说起 技术 原因,只有少数,非常具体,很少使用。很可能你永远不会在生活中使用它们。
也许我太无知了,但我从未有机会使用它们之类的东西
如果你需要它们 - 这些毫无疑问是技术上的原因,从mysql扩展转向更时尚和现代化的东西。
然而,也存在一些非技术性问题,这些问题可能会让您的体验更加艰难
后一个问题是一个问题。
但是,在我看来,提出的解决方案也不是更好。
在我看来 太理想化了 梦想所有这些PHP用户将学习如何正确处理SQL查询。很可能他们只是机械地将mysql_ *改为mysqli_ *, 离开方法是一样的。特别是因为mysqli使准备好的语句使用令人难以置信的痛苦和麻烦。
更不用说了 本地人 准备好的陈述 不足以保护 从SQL注入开始,mysqli和PDO都没有提供解决方案。
所以,与其打击这种诚实的延伸,我宁愿采取错误的做法,并以正确的方式教育人们。
此外,有一些错误或非重要的原因,如
mysql_query("CALL my_proc"); 很长时间)最后一个是有趣的一点。虽然mysql ext不支持 本地人 准备好的声明,它们不是安全所必需的。我们可以使用手动处理的占位符轻松伪造准备好的语句(就像PDO一样):
function paraQuery()
{
$args = func_get_args();
$query = array_shift($args);
$query = str_replace("%s","'%s'",$query);
foreach ($args as $key => $val)
{
$args[$key] = mysql_real_escape_string($val);
}
$query = vsprintf($query, $args);
$result = mysql_query($query);
if (!$result)
{
throw new Exception(mysql_error()." [$query]");
}
return $result;
}
$query = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);
瞧,一切都是参数化和安全的。
但好吧,如果您不喜欢手册中的红色框,会出现选择问题:mysqli或PDO?
嗯,答案如下:
如果像绝大多数PHP人一样,你在应用程序代码中使用原始API调用(这实际上是错误的做法) - PDO是唯一的选择,因为这个扩展假装不只是API而是半DAL,仍然不完整但提供了许多重要的功能,其中两个使得PDO与mysqli有着明显的区别:
因此,如果您是普通的PHP用户并希望在使用本机预处理语句时节省大量的麻烦,那么再次使用PDO是唯一的选择。
然而,PDO也不是银弹,并且有其艰辛。
所以,我为所有常见的陷阱和复杂案例编写了解决方案 PDO标签维基
尽管如此,每个谈论扩展的人总是错过了 2个重要事实 关于Mysqli和PDO:
准备好的声明 不是银弹。有动态标识符不能使用预准备语句绑定。存在具有未知数量的参数的动态查询,这使得查询构建成为困难的任务。
mysqli_ *和PDO函数都不应该出现在应用程序代码中。
应该有一个 抽象层 它们和应用程序代码之间,它将完成内部绑定,循环,错误处理等所有脏工作,使应用程序代码DRY和清理。特别是对于像动态查询构建这样的复杂情况。
所以,仅仅切换到PDO或mysqli是不够的。必须使用ORM,查询构建器或任何数据库抽象类,而不是在其代码中调用原始API函数。
相反 - 如果你的应用程序代码和mysql API之间有一个抽象层 - 使用哪种发动机实际上并不重要。 您可以使用mysql ext直到它被弃用,然后轻松地将您的抽象类重写为另一个引擎, 使所有应用程序代码完好无损。
以下是一些基于我的例子 safemysql类 展示这样一个抽象类应该如何:
$city_ids = array(1,2,3);
$cities = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);
比较这一行与 PDO需要的代码量。
然后比较 疯狂的代码量 你将需要与原始的Mysqli准备的声明。
请注意,错误处理,分析,查询日志已经内置并运行。
$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);
将它与通常的PDO插入进行比较,当每个字段名称重复六到十次时 - 在所有这些众多的命名占位符,绑定和查询定义中。
另一个例子:
$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);
你很难找到PDO处理这种实际案例的例子。
而且它太过于罗嗦而且很可能不安全。
所以,再一次 - 不仅仅是原始驱动程序应该是你的关注而是抽象类,不仅对初学者手册中的愚蠢例子有用,而且对解决任何现实生活中的问题都很有用。
原因很多,但也许最重要的原因是这些功能鼓励不安全的编程实践,因为它们不支持预处理语句。准备好的语句有助于防止SQL注入攻击。
使用时 mysql_* 函数,你必须记住通过运行用户提供的参数 mysql_real_escape_string()。如果您只是在一个地方忘记或者您碰巧只是逃避了部分输入,那么您的数据库可能会受到攻击。
使用准备好的语句 PDO 要么 mysqli 将使这些编程错误更难以制作。
因为(除其他原因外)确保输入数据被消毒要困难得多。如果您使用参数化查询,就像使用PDO或mysqli一样,您可以完全避免风险。
例如,有人可以使用 "enhzflep); drop table users" 作为用户名。旧函数将允许每个查询执行多个语句,因此类似讨厌的bugger可以删除整个表。
如果一个人使用mysqli的PDO,那么用户名将最终成为 "enhzflep); drop table users"。
看到 bobby-tables.com。
这个答案是为了说明绕过写得不好的PHP用户验证代码,如何(和使用什么)这些攻击工作以及如何用安全的预处理语句替换旧的MySQL函数是多么微不足道 - 基本上,为什么StackOverflow用户(可能有很多代表)正在咆哮新用户提出问题以改进他们的代码。
首先,请随意创建这个测试mysql数据库(我已经打过我的准备):
mysql> create table users(
-> id int(2) primary key auto_increment,
-> userid tinytext,
-> pass tinytext);
Query OK, 0 rows affected (0.05 sec)
mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)
mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)
mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)
完成后,我们可以转到我们的PHP代码。
让我们假设以下脚本是网站管理员的验证过程(简化但是如果您复制并使用它进行测试则有效):
<?php
if(!empty($_POST['user']))
{
$user=$_POST['user'];
}
else
{
$user='bob';
}
if(!empty($_POST['pass']))
{
$pass=$_POST['pass'];
}
else
{
$pass='bob';
}
$database='prep';
$link=mysql_connect('localhost', 'prepared', 'example');
mysql_select_db($database) or die( "Unable to select database");
$sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
//echo $sql."<br><br>";
$result=mysql_query($sql);
$isAdmin=false;
while ($row = mysql_fetch_assoc($result)) {
echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
$isAdmin=true;
// We have correctly matched the Username and Password
// Lets give this person full access
}
if($isAdmin)
{
echo "The check passed. We have a verified admin!<br>";
}
else
{
echo "You could not be verified. Please try again...<br>";
}
mysql_close($link);
?>
<form name="exploited" method='post'>
User: <input type='text' name='user'><br>
Pass: <input type='text' name='pass'><br>
<input type='submit'>
</form>
乍一看似乎足够合法。
用户必须输入登录名和密码,对吧?
很棒,不要输入以下内容:
user: bob
pass: somePass
并提交。
输出如下:
You could not be verified. Please try again...
超!按预期工作,现在让我们尝试实际的用户名和密码:
user: Fluffeh
pass: mypass
惊人!全面的Hi-fives,代码正确验证了管理员。这是完美的!
嗯,不是真的。让我们说用户是一个聪明的小人物。让我们说这个人就是我。
输入以下内容:
user: bob
pass: n' or 1=1 or 'm=m
输出是:
The check passed. We have a verified admin!
恭喜,您刚刚允许我输入您的超级保护管理员部分,我输入了错误的用户名和虚假密码。说真的,如果你不相信我,用我提供的代码创建数据库,并运行这个PHP代码 - 一眼看上去似乎确实很好地验证了用户名和密码。
所以,作为回答,那就是为什么你会被骂。
所以,让我们来看看出了什么问题,以及为什么我刚刚进入你的超级管理员蝙蝠洞穴。我猜了一下,并假设你没有小心你的输入,只是直接将它们传递给数据库。我以一种改变您实际运行的查询的方式构造输入。那么,它应该是什么样的,最终是什么呢?
select id, userid, pass from users where userid='$user' and pass='$pass'
这是查询,但是当我们用我们使用的实际输入替换变量时,我们得到以下结果:
select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'
看看我如何构建我的“密码”,以便它首先关闭密码周围的单引号,然后引入一个全新的比较?然后为了安全起见,我添加了另一个“字符串”,以便单引号在我们原来的代码中按预期关闭。
但是,这不是关于人们现在大吼大叫,这是关于向您展示如何使您的代码更安全。
好的,那么出了什么问题,我们该如何解决呢?
这是一种典型的SQL注入攻击。最简单的事情之一。在攻击向量的范围内,这是一个蹒跚学步的攻击坦克 - 并获胜。
那么,我们如何保护您的神圣管理部分并使其变得美观和安全?要做的第一件事就是停止使用那些真正旧的和不赞成的 mysql_* 功能。我知道,你按照你在网上找到的教程,它可以工作,但它已经过时了,它已经过时了,在几分钟的时间里,我刚刚打破过它而没有打破汗水。
现在,您有更好的使用选择 mysqli_ 要么 PDO。我个人是PDO的忠实粉丝,所以我将在其余的答案中使用PDO。有赞成和反对意见,但我个人认为专业人士远远超过了骗局。它可以在多个数据库引擎中移植 - 无论您使用的是MySQL还是Oracle,或者只是通过更改连接字符串,只需更改连接字符串,它具有我们想要使用的所有奇特功能,而且非常干净。我喜欢干净。
现在,让我们再看看那段代码,这次使用PDO对象编写:
<?php
if(!empty($_POST['user']))
{
$user=$_POST['user'];
}
else
{
$user='bob';
}
if(!empty($_POST['pass']))
{
$pass=$_POST['pass'];
}
else
{
$pass='bob';
}
$isAdmin=false;
$database='prep';
$pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
$sql="select id, userid, pass from users where userid=:user and pass=:password";
$myPDO = $pdo->
热门问题
不使用eval / new函数的JavaScript模板库
当涉及内部类时,Java继承如何工作
.NET Windows服务的奇怪问题
在.ipa或.app下查找App ID
快速入门XSLT参考[关闭]
如何找出Android应用程序中未使用的资源
Ruby中并发的同步方法[重复]
将std :: chrono :: system_clock :: time_point转换为struct timeval并返回
Google Drive API V3(javascript)更新文件内容
Bootstrap 3.0 - 将元素推到底部
受密码保护的.NET ClickOnce部署?
如何用postgresql安装wordpress
coq Set或Type如何成为命题
硒滚动元素进入(中心)视图
在Spring Transaction JUnit测试中自动装配Hibernate会话的正确方法
Git的Dockerfile策略
如何在FOS_PICKFOLDER中使用IFileDialog,同时仍在对话框中显示文件名
在Firefox扩展中复制Google Chrome浏览器操作弹出效果
CakePHP找到MAX
芹菜 - 完成任务的召唤功能
从使用fmemopen创建的流中读取宽字符
.NET是否为每个程序集创建一个字符串实习池?
DefaultModelBinder不绑定嵌套模型
Navigator.MediaDevices.getUserMedia()使用了哪些相机通信标准?
选择命名空间名称时应该知道什么?
cout
Swagger Codegen CLI Java客户端 - 如何正确使用它
一个很好的哈希函数用于采访整数,字符串?
Maven 3 ciManagement配置的目的是什么?
如何通过语言文化获取代码页?