【问题标题】:prevent sql injection in mysqli [duplicate]防止mysqli中的sql注入[重复]
【发布时间】:2016-04-06 14:41:51
【问题描述】:

我之前对 mysqli 很陌生,我在 mysql 中编写查询,但 mysqli 更高级,所以我是第一次使用它。 下面是我的php代码。

    function clean($str) {
        $str = @trim($str);
        if(get_magic_quotes_gpc()) {
            $str = stripslashes($str);
        }
        return mysql_real_escape_string($str);
    }

        $email = clean($_POST['email']);
        $password = clean($_POST['password']);
        //$password =md5($password);


    if(empty($res['errors'])) {
        $result = $mysqli->query("SELECT uid FROM users where email='$email' and password = '$password'");
        if($result->num_rows == 1){
            $res['success'] = true;
        }
        else{
            array_push($res['errors'], 'Invalid login details');
            $res['success'] = false;

        }
    }else{
        $res['success'] = false;        
    }
    echo json_encode($res);
}

clean 函数没有按预期工作,因为如果我输入正确的用户名和密码,sql 查询将返回 false。 因此,这似乎在 mysqli 案例中无效。

我检查了这个链接PHP MySQLI Prevent SQL Injection 并知道我们必须准备查询。

我可以看到有一个示例,但如果我必须使用两个或更多表单数据,我无法理解如何准备/绑定。

感谢您的宝贵时间。

更新代码

$result = $mysqli->prepare("SELECT uid FROM users where email=:email and password = :password");
        $result->execute([
':email' => $email,
         ':password' => $password]);
        //$result->execute();
        if($result->num_rows == 1){
        //if(mysqli_num_rows($result) === 1) {
            $res['success'] = true;

        }
        else{
            array_push($res['errors'], 'Invalid login details');
            $res['success'] = false;

        }

【问题讨论】:

  • 使用基于pdo的redbeanphp。
  • 原因是,您正在混合 MySQL API,如果您仍在使用 return mysql_real_escape_string($str); 与您的现有/新代码,则不能这样做。只需摆脱所有这些并使用准备好的语句。另外,我们不知道您的 POST 数组中是否有任何值,因为您没有发布 HTML 表单。
  • 我们也不知道您(现在)使用哪个 API 进行连接。 mysql_? mysqli_? PDO?其他?然后你有if($result->num_rows == 1){mysqli_ 语法,而你的where email=:email and password = :password 是PDO。同样,它们不会混合使用。

标签: php mysqli


【解决方案1】:

正如 cmets 中所述,您需要与您的 API 选择保持一致。 You can't mix APIs in PHP

您从mysqli_* 开始,所以我将继续。你有一些 mysql_* 和 PDO 在那里,使用 PDO 而不是 mysqli_* 可能不是一个坏主意 - 但如果你的服务器支持 mysqli_*,那么使用它没有任何问题。请参阅Choosing an API 并自行决定(远离mysql_*,它已经过时了)。

使用mysqli_*,您可以像这样连接到数据库(您没有显示您的连接)。

$mysqli = new mysqli("host", "username", "password", "database");
if ($mysqli->connect_errno) {
    echo "Failed to connect to MySQL: (".$mysqli->connect_errno.") ".$mysqli->connect_error;
}
$mysqli->set_charset("utf8");

至于自己防止SQL注入,你只需要使用准备好的语句。如果您不想在表格中放置某种值,您仍然可以清理或清理数据 - 但这是另一种讨论。

您还需要知道您的密码是否在数据库中进行了哈希处理。 确实应该是,如果您使用的是 PHP5.5 及更高版本,则应该使用 password_hash($password, $algorithm)password_verify($password, $hash)(如果不是,请查看 password_compat 之类的内容)。

您也需要与您的哈希一致,您不能使用md5 插入它并选择没有哈希的它。这一切都需要相同。因为如果你选择一个md5 哈希,并将它与一个未哈希的字符串进行比较,它们会有所不同,并且查询会失败。

我正在向您展示使用password_verify() 的示例,这意味着存储在数据库中的密码也需要与password_hash() 一起存储(否则您的查询将失败)。

if ($stmt = $mysqli->prepare("SELECT uid, password FROM users where email=?")) {
    $stmt->bind_param("s", $_POST['email']);           // Bind variable to the placeholder
    $stmt->execute();                                 // Execute query
    $stmt->bind_result($userID, $password);         // Set the selected columns into the variables
    $stmt->fetch();                                   // ...and fetch it
    if ($stmt->num_rows) {
        if (password_verify($_POST['password'], $password)) {
            // Password was correct and matched the email!
        } else {
            // Password was incorrect...
        }
    } else {
        // Accountname not found
    } 
}

这只是一个基本示例,但它可以帮助您入门。 永远不要相信用户输入,使用准备好的语句。

【讨论】:

    【解决方案2】:

    您可以像这样绑定更多变量:

    $stmt = $mysqli->prepare("SELECT uid FROM users where email= ? and password = ?");
    $stmt->bind_param('ss', $email, $password);
    
    /* execute prepared statement */
    $stmt->execute();
    

    如您所见,您可以扩展bind_param() 函数。您还可以添加不同类型的变量:

    i   corresponding variable has type integer
    d   corresponding variable has type double
    s   corresponding variable has type string
    b   corresponding variable is a blob and will be sent in packets
    

    发件人:http://php.net/manual/en/mysqli-stmt.bind-param.php

    【讨论】:

    • 感谢您的回答。我会尝试。投票赞成。
    • 仍然给我同样的错误。用户名密码无效。
    【解决方案3】:

    首先,我建议你学习PDO而不是MySQLi,因为它支持更多的驱动程序。

    其次,你使用 mysql_real_escape_string,正如你所见,这是一个 MySQL 函数,而不是 MySQLi 函数。

    那么你在哪里:

    $result = $mysqli->query("SELECT uid FROM users where email='$email' and password = '$password'");
    

    你应该这样做:

    <?php
    $stmt = $dbConnection->prepare("SELECT uid FROM users where email = :email AND password = :password");
    try{
    $stmt->execute([
        ':email' => $email,
        ':password' => $password
    ]);
    }
    catch(Exception $e){
        echo $e->getMessage(); //Remove when putting online
    }
    
    if($stmt->num_rows){
        $res['success'] = true;
    }
    ?>
    

    【讨论】:

    • 感谢您的回答。首先,我将学习 mysqli,然后我将转到 PDO。投票赞成。
    • @404 如果对您有帮助,请点击 V 按钮下方的按钮将其标记为答案。它也将在未来帮助其他人。我还想指出,据我所知,您不必再使用 clean() 函数了。
    • 您能否告诉我如何在查询中使用电子邮件和密码。你只给了我电子邮件的例子。
    • 只需添加与电子邮件相同的内容,只需在查询和执行中将单词更改为密码即可。
    • 我试过但没有用。我正在更新相关代码。
    【解决方案4】:

    您目前正在将 MySQL API/函数与 mysql_real_escape_string()num_rows 和 PDO 绑定方法 where email=:email and password = :password 混合,这似乎取自针对您的问题给出的另一个答案。

    • 这些不同的功能不会混合使用。

    从连接到查询必须使用同一个。

    您似乎想要设置登录脚本。我建议您使用以下内容并从 ircmaxell 的答案之一中提取:

    来自https://stackoverflow.com/a/29778421/

    只需使用库。严重地。它们的存在是有原因的。

    不要自己做。如果您正在创建自己的盐,您做错了。您应该使用一个为您处理的库。

    $dbh = new PDO(...);
    
    $username = $_POST["username"];
    $email = $_POST["email"];
    $password = $_POST["password"];
    $hash = password_hash($password, PASSWORD_DEFAULT);
    
    $stmt = $dbh->prepare("insert into users set username=?, email=?, password=?");
    $stmt->execute([$username, $email, $hash]);
    

    在登录时:

    $sql = "SELECT * FROM users WHERE username = ?";
    $stmt = $dbh->prepare($sql);
    $result = $stmt->execute([$_POST['username']]);
    $users = $result->fetchAll();
    if (isset($users[0]) {
        if (password_verify($_POST['password'], $users[0]->password) {
            // valid login
        } else {
            // invalid password
        }
    } else {
        // invalid username
    }
    

    它更安全并使用安全的密码哈希方法,而不是您似乎想要使用的是 MD5 $password =md5($password); 并且现在不再认为可以安全使用。

    参考资料:

    旁注:如果您确实走这条路,请记住阅读手册,并且您的密码列足够长以容纳散列。最小长度为 60,但他们推荐 255。

    还不清楚您的 HTML 表单是否具有 POST 数组的名称属性,因此请确保该表单使用 POST 方法。

    我相信我已经为您提供了足够的信息来开始使用。


    您不能做的是将上述代码与您当前的代码一起使用并简单地修补它。你需要重新开始。


    error reporting 添加到文件顶部,这将有助于查找错误。

    <?php 
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    
    // rest of your code
    

    旁注:只应在暂存阶段显示错误,而不是在生产阶段。

    【讨论】:

    • 感谢您的回答。我正在学习它,并将努力提高我的知识。
    • @404 不客气。
    • 如果我们创建自己的hasing函数有什么缺点?
    • @404 确实需要更多的工作。为什么不直接使用已经编写好的、久经考验的功能呢? ;-) 你最终可能会创建可能被黑客破解的代码。
    猜你喜欢
    • 2012-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 2015-05-04
    • 2016-02-19
    • 2014-08-18
    相关资源
    最近更新 更多