为什么不应该使用mysql_*函数的技术原因是什么?(例如mysql_query()、mysql_connect()或mysql_real_ascape_string())?

为什么我要使用其他东西,即使它们在我的网站上工作?

如果它们在我的网站上不起作用,为什么我会出现这样的错误

警告:mysql_connect():没有这样的文件或目录


当前回答

MySQL扩展是三个扩展中最古老的,也是开发人员用来与MySQL通信的原始方式。由于PHP和MySQL的更新版本都有改进,因此现在不推荐使用此扩展,而支持其他两种替代方案。

MySQLi是用于处理MySQL数据库的“改进”扩展。它利用了MySQL服务器较新版本中可用的功能,向开发人员公开了面向功能和面向对象的界面,并做了一些其他漂亮的事情。PDO提供了一个API,它整合了以前分布在主要数据库访问扩展(即MySQL、PostgreSQL、SQLite、MSSQL等)中的大部分功能。该接口为程序员提供高级对象,以处理数据库连接、查询和结果集,低级驱动程序与数据库服务器进行通信和资源处理。PDO正在进行大量的讨论和工作,它被认为是使用现代专业代码处理数据库的适当方法。

其他回答

因为(除其他原因外)要确保输入数据得到净化要困难得多。如果您使用参数化查询,就像使用PDO或mysqli一样,您可以完全避免风险。

例如,有人可以使用“enhzflep);drop table users”作为用户名。旧的函数将允许每个查询执行多个语句,所以像这种讨厌的bug可以删除整个表。

如果要使用mysqli的PDO,用户名将以“enhzflep);drop table user”结尾。

参见bobby-tables.com。

编写这个答案是为了说明绕过编写糟糕的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...

这就是为什么你在发布问题时会被吼——这是因为人们可以看到你的代码可以被绕过,甚至不用尝试。请使用这个问题和答案来改进代码,使其更安全,并使用当前的功能。

最后,这并不是说这是完美的代码。你可以做更多的事情来改进它,例如,使用散列密码,确保当你在数据库中存储敏感信息时,你不会以纯文本形式存储,并具有多个级别的验证-但实际上,如果你只是将旧的易注入代码改为这样,在编写好代码的过程中,你会很好的——事实上,你已经走到了这一步,而且还在阅读,这让我有一种希望,你不仅会在编写网站和应用程序时实现这种类型的代码,而且你可能会出去研究我刚才提到的其他东西——还有更多。写出你能写的最好的代码,而不是最基本的代码。

不要使用mysql,因为不推荐使用Mysqli。

弃用的含义:

这意味着不要使用某些特定的功能/方法/软件功能/特定的软件实践,这只是意味着不应该使用它,因为该软件中有(或将有)更好的替代方案,而应该使用它。

使用不推荐使用的函数时可能会出现以下几个常见问题:

1.功能完全停止工作:应用程序或脚本可能依赖于不再支持的功能,因此使用其改进版本或替代版本。

2.显示有关弃用的警告消息:这些消息通常不会干扰站点功能。然而,在某些情况下,它们可能会中断服务器发送标头的过程。

例如:这可能会导致登录问题(cookie/会话设置不正确)或转发问题(301/302/303重定向)。

请记住:

-弃用的软件仍然是软件的一部分。

-弃用的代码只是代码的状态(标签)。

MYSQL与MYSQLI的主要区别mysql数据库*

旧数据库驱动程序MySQL只能按程序使用没有针对SQL注入攻击的保护在PHP 5.5.0中被弃用,在PHP 7中被删除

mysqli数据库

新数据库驱动程序当前正在使用准备好的语句可防止攻击

可以使用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_函数:

已过时-不再维护不允许您轻松移动到另一个数据库后端不支持准备好的语句,因此鼓励程序员使用串联来构建查询,从而导致SQL注入漏洞