为什么不应该使用mysql_*函数的技术原因是什么?(例如mysql_query()、mysql_connect()或mysql_real_ascape_string())?
为什么我要使用其他东西,即使它们在我的网站上工作?
如果它们在我的网站上不起作用,为什么我会出现这样的错误
警告:mysql_connect():没有这样的文件或目录
为什么不应该使用mysql_*函数的技术原因是什么?(例如mysql_query()、mysql_connect()或mysql_real_ascape_string())?
为什么我要使用其他东西,即使它们在我的网站上工作?
如果它们在我的网站上不起作用,为什么我会出现这样的错误
警告:mysql_connect():没有这样的文件或目录
当前回答
MySQL扩展:
未积极开发自PHP 5.5(2013年6月发布)起正式弃用。自PHP 7.0(2015年12月发布)起已完全删除这意味着截至2018年12月31日,它不存在于任何支持的PHP版本中。如果您使用的是支持它的PHP版本,那么您使用的版本不会解决安全问题。缺少OO接口不支持:非阻塞异步查询准备好的语句或参数化查询存储过程多个语句交易“新”密码身份验证方法(默认情况下,在MySQL 5.6中打开;5.7中需要)MySQL 5.1或更高版本中的任何新功能
由于它已被弃用,因此使用它会使您的代码不那么经得起未来考验。
缺少对准备好的语句的支持尤为重要,因为与使用单独的函数调用手动转义外部数据相比,它们提供了一种更清晰、更不易出错的转义和引用外部数据的方法。
请参阅SQL扩展的比较。
其他回答
说到技术原因,只有几个,非常具体,很少使用。很可能你一生中都不会使用它们。也许我太无知了,但我从来没有机会使用它们
非阻塞异步查询返回多个结果集的存储过程加密(SSL)压缩
如果您需要,这些无疑是从mysql扩展转向更时尚、更现代的技术原因。
尽管如此,也有一些非技术性的问题,这会使您的体验更加困难
在现代PHP版本中进一步使用这些函数将引起不推荐使用的级别通知。它们可以简单地关闭。在不久的将来,它们可能会从默认的PHP构建中删除。也没什么大不了的,因为mydsql-ext将被迁移到PECL中,每个宿主都会很乐意用它来编译PHP,因为他们不想失去那些网站工作了几十年的客户。Stackoverflow社区的强烈抵抗。每次你提到这些诚实的功能时,你都会被告知它们是严格的禁忌。作为一个普通的PHP用户,您使用这些函数的想法很可能是错误的。就因为有这么多的教程和手册教你错误的方法。不是函数本身-我必须强调它-而是它们的使用方式。
后一个问题是一个问题。但是,在我看来,提议的解决方案也没有更好。在我看来,所有这些PHP用户都将立即学习如何正确处理SQL查询,这是一个过于理想化的梦想。他们很可能只是机械地将mysql_*更改为mysqli_*,保持方法不变。特别是因为mysqli使准备好的语句使用起来非常痛苦和麻烦。更不用说,本机准备的语句不足以防止SQL注入,mysqli和PDO都没有提供解决方案。
因此,我宁愿反对错误的做法,以正确的方式教育人们,而不是反对这种诚实的延伸。
此外,还有一些错误或不重要的原因,如
不支持存储过程(我们使用的是mysql_query(“CALL my_proc”);年龄)不支持交易(同上)不支持多个语句(谁需要它们?)不在积极发展中(那么,它对你有什么实际影响吗?)缺少OO接口(创建一个OO接口需要几个小时)不支持准备语句或参数化查询
最后一点很有趣。虽然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?
答案如下:
如果您了解使用数据库抽象层并寻找API来创建抽象层的必要性,那么mysqli是一个非常好的选择,因为它确实支持许多mysql特有的特性。如果像绝大多数PHP用户一样,您在应用程序代码中正确使用原始API调用(这基本上是错误的做法),那么PDO是唯一的选择,因为这个扩展假装不仅仅是API,而是一个半DAL,仍然不完整,但提供了许多重要功能,其中两个功能使PDO与mysqli有着重要区别:与mysqli不同,PDO可以通过值绑定占位符,这使得动态构建的查询变得可行,而无需几个屏幕的代码。与mysqli不同,PDO总是可以在一个简单的普通数组中返回查询结果,而mysqli只能在mysqlnd安装上执行。
因此,如果你是一个普通的PHP用户,并且想在使用本机准备的语句时省去一大堆麻烦,那么PDO是唯一的选择。然而,PDO也不是银弹,也有其困难。因此,我为PDO标签wiki中的所有常见陷阱和复杂案例编写了解决方案
然而,每个谈论扩展的人总是忽略了关于Myqli和PDO的两个重要事实:
事先准备好的声明不是灵丹妙药。有一些动态标识符不能使用准备好的语句绑定。存在具有未知数量参数的动态查询,这使得查询构建成为一项困难的任务。mysqli_*和PDO函数都不应该出现在应用程序代码中。在它们和应用程序代码之间应该有一个抽象层,它将在内部完成绑定、循环、错误处理等所有肮脏的工作,使应用程序代码变得干干净净。特别是对于动态查询构建这样的复杂情况。
因此,仅仅切换到PDO或mysqli是不够的。必须使用ORM、查询生成器或任何数据库抽象类,而不是在代码中调用原始API函数。相反,如果在应用程序代码和mysqlAPI之间有一个抽象层,那么使用哪个引擎实际上并不重要。您可以使用mysql-ext,直到它被弃用,然后轻松地将抽象类重写到另一个引擎,使所有应用程序代码保持完整。
下面是基于我的safemysql类的一些示例,以说明这样的抽象类应该是什么样子的:
$city_ids = array(1,2,3);
$cities = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);
将这一行与PDO所需的代码量进行比较。然后,将您需要的大量代码与原始Myqli准备的语句进行比较。请注意,错误处理、分析和查询日志已经内置并正在运行。
$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处理此类实际案例的示例。而且这太啰嗦了,很可能是不安全的。
所以,再一次,你应该关注的不仅仅是原始驱动程序,而是抽象类,它不仅适用于初学者手册中的愚蠢示例,也适用于解决任何现实问题。
mysql_函数:
已过时-不再维护不允许您轻松移动到另一个数据库后端不支持准备好的语句,因此鼓励程序员使用串联来构建查询,从而导致SQL注入漏洞
可以使用mysqli或PDO定义几乎所有的mysql_*函数。只需在旧的PHP应用程序上包含它们,它就可以在PHP7上运行。我的解决方案在这里。
<?php
define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;
function mysql_link($link=null) {
return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}
function mysql_connect($host, $user, $pass) {
$GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
return $GLOBALS[MYSQL_LINK];
}
function mysql_pconnect($host, $user, $pass) {
return mysql_connect($host, $user, $pass);
}
function mysql_select_db($db, $link=null) {
$link = mysql_link($link);
return mysqli_select_db($link, $db);
}
function mysql_close($link=null) {
$link = mysql_link($link);
return mysqli_close($link);
}
function mysql_error($link=null) {
$link = mysql_link($link);
return mysqli_error($link);
}
function mysql_errno($link=null) {
$link = mysql_link($link);
return mysqli_errno($link);
}
function mysql_ping($link=null) {
$link = mysql_link($link);
return mysqli_ping($link);
}
function mysql_stat($link=null) {
$link = mysql_link($link);
return mysqli_stat($link);
}
function mysql_affected_rows($link=null) {
$link = mysql_link($link);
return mysqli_affected_rows($link);
}
function mysql_client_encoding($link=null) {
$link = mysql_link($link);
return mysqli_character_set_name($link);
}
function mysql_thread_id($link=null) {
$link = mysql_link($link);
return mysqli_thread_id($link);
}
function mysql_escape_string($string) {
return mysql_real_escape_string($string);
}
function mysql_real_escape_string($string, $link=null) {
$link = mysql_link($link);
return mysqli_real_escape_string($link, $string);
}
function mysql_query($sql, $link=null) {
$link = mysql_link($link);
return mysqli_query($link, $sql);
}
function mysql_unbuffered_query($sql, $link=null) {
$link = mysql_link($link);
return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}
function mysql_set_charset($charset, $link=null){
$link = mysql_link($link);
return mysqli_set_charset($link, $charset);
}
function mysql_get_host_info($link=null) {
$link = mysql_link($link);
return mysqli_get_host_info($link);
}
function mysql_get_proto_info($link=null) {
$link = mysql_link($link);
return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
$link = mysql_link($link);
return mysqli_get_server_info($link);
}
function mysql_info($link=null) {
$link = mysql_link($link);
return mysqli_info($link);
}
function mysql_get_client_info() {
$link = mysql_link();
return mysqli_get_client_info($link);
}
function mysql_create_db($db, $link=null) {
$link = mysql_link($link);
$db = str_replace('`', '', mysqli_real_escape_string($link, $db));
return mysqli_query($link, "CREATE DATABASE `$db`");
}
function mysql_drop_db($db, $link=null) {
$link = mysql_link($link);
$db = str_replace('`', '', mysqli_real_escape_string($link, $db));
return mysqli_query($link, "DROP DATABASE `$db`");
}
function mysql_list_dbs($link=null) {
$link = mysql_link($link);
return mysqli_query($link, "SHOW DATABASES");
}
function mysql_list_fields($db, $table, $link=null) {
$link = mysql_link($link);
$db = str_replace('`', '', mysqli_real_escape_string($link, $db));
$table = str_replace('`', '', mysqli_real_escape_string($link, $table));
return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}
function mysql_list_tables($db, $link=null) {
$link = mysql_link($link);
$db = str_replace('`', '', mysqli_real_escape_string($link, $db));
return mysqli_query($link, "SHOW TABLES FROM `$db`");
}
function mysql_db_query($db, $sql, $link=null) {
$link = mysql_link($link);
mysqli_select_db($link, $db);
return mysqli_query($link, $sql);
}
function mysql_fetch_row($qlink) {
return mysqli_fetch_row($qlink);
}
function mysql_fetch_assoc($qlink) {
return mysqli_fetch_assoc($qlink);
}
function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
return mysqli_fetch_array($qlink, $result);
}
function mysql_fetch_lengths($qlink) {
return mysqli_fetch_lengths($qlink);
}
function mysql_insert_id($qlink) {
return mysqli_insert_id($qlink);
}
function mysql_num_rows($qlink) {
return mysqli_num_rows($qlink);
}
function mysql_num_fields($qlink) {
return mysqli_num_fields($qlink);
}
function mysql_data_seek($qlink, $row) {
return mysqli_data_seek($qlink, $row);
}
function mysql_field_seek($qlink, $offset) {
return mysqli_field_seek($qlink, $offset);
}
function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
return ($params === null)
? mysqli_fetch_object($qlink, $class)
: mysqli_fetch_object($qlink, $class, $params);
}
function mysql_db_name($qlink, $row, $field='Database') {
mysqli_data_seek($qlink, $row);
$db = mysqli_fetch_assoc($qlink);
return $db[$field];
}
function mysql_fetch_field($qlink, $offset=null) {
if ($offset !== null)
mysqli_field_seek($qlink, $offset);
return mysqli_fetch_field($qlink);
}
function mysql_result($qlink, $offset, $field=0) {
if ($offset !== null)
mysqli_field_seek($qlink, $offset);
$row = mysqli_fetch_array($qlink);
return (!is_array($row) || !isset($row[$field]))
? false
: $row[$field];
}
function mysql_field_len($qlink, $offset) {
$field = mysqli_fetch_field_direct($qlink, $offset);
return is_object($field) ? $field->length : false;
}
function mysql_field_name($qlink, $offset) {
$field = mysqli_fetch_field_direct($qlink, $offset);
if (!is_object($field))
return false;
return empty($field->orgname) ? $field->name : $field->orgname;
}
function mysql_field_table($qlink, $offset) {
$field = mysqli_fetch_field_direct($qlink, $offset);
if (!is_object($field))
return false;
return empty($field->orgtable) ? $field->table : $field->orgtable;
}
function mysql_field_type($qlink, $offset) {
$field = mysqli_fetch_field_direct($qlink, $offset);
return is_object($field) ? $field->type : false;
}
function mysql_free_result($qlink) {
try {
mysqli_free_result($qlink);
} catch (Exception $e) {
return false;
}
return true;
}
易于使用
分析和综合原因已经提到。对于新手来说,停止使用过时的mysql_函数是一个更重要的激励因素。
现代数据库API更易于使用。
主要是绑定参数可以简化代码。而且有了优秀的教程(如上所述),向PDO的过渡并不太困难。
然而,一次重写更大的代码库需要时间。Raison d‘être表示此中间选项:
替代mysql的等效pdo_*函数_*
使用<pdo_mysql.php>,您可以毫不费力地切换旧的mysql_函数。它添加了pdo_函数包装器,取代了mysql_对应的包装器。
只需包含一次(“pdo_mysql.php”);在必须与数据库交互的每个调用脚本中。删除所有地方的mysql_函数前缀,并将其替换为pdo。mysql_connect()变为pdo_connect()mysql_query()变为pdo_query()mysql_num_rows()变为pdo_num_rowsmysql_insert_id()变为pdo_insert_id()mysql_fetch_array()变为pdo_fetch_arraymysql_fetch_assoc()变为pdo_fetch_assocmysql_real_ascape_string()变为pdo_real_escape_string()等等您的代码工作方式相同,但大部分看起来仍然相同:includeonce(“pdo_mysql.php”);pdo_connect(“localhost”、“usrABC”、“pw1234567”);pdo_select_db(“测试”);$result=pdo_query(“SELECT title,html FROM pages”);而($row=pdo_fetch_assoc($result)){打印“$row[title]-$row[html]”;}
等等。您的代码正在使用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_*仍然允许或。只是不要转义变量并将其绑定到同一个查询中。
占位符功能由其背后的真实PDO提供。因此也允许:稍后命名占位符列表。
更重要的是,您可以在任何查询后安全地传递$_REQUEST[]变量。当提交<form>字段与数据库结构完全匹配时,它甚至更短:
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
太简单了。但是,让我们回到一些重写建议和技术原因,来解释为什么你可能想要摆脱mysql并逃离。
修复或删除任何旧的学校消毒()函数
将所有mysql_调用转换为带有绑定参数的pdo_query后,删除所有冗余的pdo_real_escape_string调用。
特别是,您应该修复任何消毒剂、清洁剂或过滤器This或clean_data函数,如过期教程以一种或另一种形式宣传的那样:
function sanitize($str) {
return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}
这里最明显的缺陷是缺少文档。更重要的是,过滤的顺序完全错误。
正确的顺序应该是:不赞成将stripse作为最内部的调用,然后trim,然后stripse_tags,htmlentities作为输出上下文,最后是_escape_string,因为它的应用程序应该直接在SQL中间存储之前。但作为第一步,只需去掉_real_aescape_string调用。如果您的数据库和应用程序流需要HTML上下文安全字符串,您可能需要暂时保留cleaning()函数的其余部分。添加一个注释,它以后只应用HTML转义。字符串/值处理委托给PDO及其参数化语句。如果在您的消毒功能中提到stripslases(),则可能表明存在更高级别的监督。这通常是为了消除不推荐使用的magic_quotes的损坏(双转义)。然而,最好是集中固定,而不是逐串固定。使用一种用户区反转方法。然后删除消毒功能中的stripslasshes()。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的变量值。
虽然这个答案强调了您应该关注删除mysql_的可读性优势。由于这种可见的技术数据/代码分离,偶尔也会有性能优势(重复的INSERT值不同)。
请注意,参数绑定仍然不是针对所有SQL注入的一站式解决方案。它处理数据/值的最常见用法。但不能白名单列名/表标识符,帮助动态子句构造,或仅列出纯数组值列表。
混合PDO使用
这些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中的其他东西,否则这是一个毫无意义的切换。我希望它被描绘得足够简单,不会加深对新人的失望。(教育通常比禁止更好。)
虽然它符合可能工作的最简单的东西,但它仍然是非常实验性的代码。我只是周末写的。然而,有太多的选择。只需搜索PHP数据库抽象并浏览一下。一直以来都有很多优秀的图书馆可以完成这样的任务。
如果您想进一步简化数据库交互,像Paris/Inidiorm这样的映射器值得一试。就像没有人在JavaScript中使用乏味的DOM一样,现在您不必照顾原始的数据库界面。
编写这个答案是为了说明绕过编写糟糕的PHP用户验证代码是多么微不足道,这些攻击是如何工作的(以及使用什么),以及如何用一个安全的准备好的语句替换旧的MySQL函数,以及基本上,为什么StackOverflow用户(可能有很多代表)会对新用户狂吠,询问如何改进他们的代码。
首先,请随意创建这个测试mysql数据库(我已经调用了我的prep):
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>
乍一看似乎足够合理。
用户必须输入登录名和密码,对吗?
Brilliant,现在输入以下内容:
user: bob
pass: somePass
并提交。
输出如下:
You could not be verified. Please try again...
超级的按照预期工作,现在让我们尝试实际的用户名和密码:
user: Fluffeh
pass: mypass
太神了大家好,代码正确地验证了管理员。太完美了!
嗯,不是真的。假设用户是一个聪明的小人物。让我们说这个人是我。
输入以下内容:
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->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
{
while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
{
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>";
}
?>
<form name="exploited" method='post'>
User: <input type='text' name='user'><br>
Pass: <input type='text' name='pass'><br>
<input type='submit'>
</form>
主要区别在于不再有mysql_*函数。这一切都是通过PDO对象完成的,其次,它使用了一个准备好的语句。现在,你问一个准备好的陈述是什么?这是一种在运行查询之前告诉数据库我们要运行的查询的方式。在这种情况下,我们告诉数据库:“嗨,我要运行一个select语句,想要表用户的id、userid和pass,其中userid是一个变量,pass也是一个变量”。
然后,在execute语句中,我们向数据库传递一个数组,其中包含它现在需要的所有变量。
结果太棒了。让我们再次尝试以前的用户名和密码组合:
user: bob
pass: somePass
未验证用户。令人惊叹的
怎么样:
user: Fluffeh
pass: mypass
哦,我只是有点兴奋,它奏效了:支票通过了。我们有一个经过验证的管理员!
现在,让我们来试试聪明的家伙会输入的数据,以试图通过我们的小验证系统:
user: bob
pass: n' or 1=1 or 'm=m
这次,我们得到了以下结果:
You could not be verified. Please try again...
这就是为什么你在发布问题时会被吼——这是因为人们可以看到你的代码可以被绕过,甚至不用尝试。请使用这个问题和答案来改进代码,使其更安全,并使用当前的功能。
最后,这并不是说这是完美的代码。你可以做更多的事情来改进它,例如,使用散列密码,确保当你在数据库中存储敏感信息时,你不会以纯文本形式存储,并具有多个级别的验证-但实际上,如果你只是将旧的易注入代码改为这样,在编写好代码的过程中,你会很好的——事实上,你已经走到了这一步,而且还在阅读,这让我有一种希望,你不仅会在编写网站和应用程序时实现这种类型的代码,而且你可能会出去研究我刚才提到的其他东西——还有更多。写出你能写的最好的代码,而不是最基本的代码。