切换至预处理语句

4

我刚开始做我的第一个项目(纯属娱乐)。我正在学习PHP和MySQL并已经完成了我的第一个工作应用程序。它能够正常运行,但现在我正在学习如何使我的应用程序更加安全,从而防止SQL注入攻击。 我大约有50多个PHP文件来管理与MySQL数据库的交互。它们的代码基本都是这样的:

<?php
$inputvalues = $_POST;
$errors = false;
$result = false;
session_start();
$uid = $_SESSION['usr_id'];
$mysqli = new mysqli('localhost', "root", "", "testdb");

if (mysqli_connect_errno()) {
        printf("Connect failed: %s\n", mysqli_connect_error());
        exit();
    }

    foreach ($inputvalues as $key => $value) {
        if(isset($value) && !empty($value)) {
            $inputvalues[$key] = $mysqli->real_escape_string( $value );
        } else {
            $errors[$key] = 'The field '.$key.' is empty';
        }
    }

    if( !$errors ) {
        $addresult = "
            SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id WHERE b.id = '".$inputvalues['schoolid']."'        
         ";

         if( $result = $mysqli->query($addresult) ) {
            while($row = $result->fetch_all())
            {
                $returnResult = $row;
            }
        }
    }
    mysqli_close($mysqli);
    echo json_encode(['result' => $returnResult, 'errors' => $errors]);
    exit;
?>

这是我在整个应用程序中用于读写数据库的格式。如果需要将它们更改为预处理语句,在不插入任何信息但只检索它们时,我该怎么办?

此外,如果我没有向数据库输入任何数据,它是否仍然容易受到注入攻击?

您能否提供一个示例,展示如何将当前代码适应于预处理语句,我将不胜感激。


存储过程不是正确的方法,你需要使用预处理语句和参数化查询...在这里解释了 https://dev59.com/oHVD5IYBdhLWcg3wL4cA#60496 - Raymond Nijland
抱歉,我修改了问题以反映出来。我对这两个术语感到困惑。 - Bruno
我提供的最后一条评论中有示例...这个问题可能会被关闭,因为这是一个重复的问题。 - Raymond Nijland
谢谢,我会阅读您提供的链接。 - Bruno
1个回答

8
我曾处于同样的情况。我也使用连接语句,后来我将应用程序切换到了预处理语句。
“坏消息”是你需要更改通过连接客户端数据到SQL语句中构建的每个SQL语句,几乎是你50个源文件中的每个SQL语句。
“好消息”是切换到预处理语句所获得的收益是无价的,例如:
1-你永远不必担心所谓的“SQL注入攻击”
PHP manual说:
如果应用程序完全使用预处理语句,“开发人员可以确保不会发生SQL注入”(但是,如果正在使用未经转义的输入构建查询的其他部分,则仍然可能发生SQL注入)。
对我来说,这个原因-安心-足以支付更改我的源代码的成本。现在你的客户可以在表单名称字段中键入robert; DROP table students; -- ;),而你可以放心,什么都不会发生。

2- 您不再需要转义客户端参数。您可以直接在SQL语句中使用它们,例如:

$query = "SELECT FROM user WHERE id = ?";
$vars[] = $_POST['id'];

替代

$id = $mysqli->real_escape_string($_POST['id']);
$query = "SELECT FROM user WHERE id = $id";

在使用预处理语句之前,你必须做的是将所有参数进行转义,否则有可能会忘记对某个参数进行转义,这会使你的系统面临被攻击者破坏的风险,只要有一个未经转义的参数就足以导致此情况发生。

改变代码

通常更改源文件总是存在风险和痛苦的,特别是如果您的软件设计不好且没有明显的测试计划。但我会告诉您,我做了什么来尽可能地简化它。

我制作了一个函数,每个数据库交互代码都将使用它,因此您可以在一个地方随意更改 -那个函数- 您可以像这样制作:

class SystemModel
{
    /**
     * @param string $query
     * @param string $types
     * @param array $vars
     * @param \mysqli $conn
     * @return boolean|$stmt
     */
    public function preparedQuery($query,$types, array $vars, $conn)
    {
        if (count($vars) > 0) {
            $hasVars = true;
        }
        array_unshift($vars, $types);
        $stmt = $conn->prepare($query);
        if (! $stmt) {
            return false;
        }
        if (isset($hasVars)) {
            if (! call_user_func_array(array( $stmt, 'bind_param'), $this->refValues($vars))) {
                return false;
            }
        }
        $stmt->execute();
        return $stmt;
    }

    /* used only inside preparedQuery */
    /* code taken from: https://stackoverflow.com/a/13572647/5407848 */
    protected function refValues($arr)
    {
        if (strnatcmp(phpversion(), '5.3') >= 0) {
            $refs = array();
            foreach ($arr as $key => $value)
                $refs[$key] = &$arr[$key];
                return $refs;
        }
        return $arr;
    }
}

现在,你可以在任何你想要的源文件中使用这个接口,例如,让我们修改你在问题中提供的当前SQL语句。让我们改变这个:

$mysqli = new mysqli('localhost', "root", "", "testdb");
$addresult = "
                SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined 
                FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id 
                WHERE b.id = '".$inputvalues['schoolid']."'";

if( $result = $mysqli->query($addresult) ) {
    while($row = $result->fetch_all())
    {
        $returnResult = $row;
    }
}

进入此

$mysqli = new mysqli('localhost', "root", "", "testdb");
$sysModel = new SystemModel();
$addresult = "
                SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined
                FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id
                WHERE b.id = ?";
$types = "i"; // for more information on paramters types, please check :
//https://php.net/manual/en/mysqli-stmt.bind-param.php
$vars = [];
$vars[] = $inputvalues['schoolid'];

$stmt = $sysModel->preparedQuery($addresult, $types, $vars, $mysqli);
if (!$stmt || $stmt->errno) {
   die('error'); // TODO: change later for a better illustrative output
}
$result = $stmt->get_result();
$returnResult = [];
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
    $returnResult[] = $row;
}

如果我没有向数据库输入任何数据,它仍然容易受到注入攻击吗? 是的,SQL注入攻击是通过将恶意字符串连接到您的SQL语句中来实现的,无论是INSERT、SELECT、DELETE还是UPDATE。例如:
$query = "SELECT * FROM user WHERE name = '{$_GET['name']}' AND password = '{$_GET['pass']}'"

这样的东西可能会被利用。

// exmaple.com?name=me&pass=1' OR 1=1; -- 

这将导致一条SQL语句

$query = "SELECT * FROM user WHERE name = 'me' AND password = '1' OR 1=1; -- '"
//executing the SQL statement and getting the result
if($result->num_rows){
    //user is authentic
}else{
    //wrong password
}
// that SQL will always get results from the table which will be considered a correct password

祝你成功地将软件转换为预处理语句,并记住,从知道无论发生什么情况,你都可以安全地免受SQL注入攻击的安心感中获得的价值,是改变源文件成本所值得的。

哇!多么详细的回答啊。我真的很感激你花时间以这种方式回答我的问题。这对我来说已经足够开始并更深入地理解为什么了。谢谢。 - Bruno
将近四年过去了,这个答案真的很好,写得很好,易于理解。谢谢! - OZ1SEJ
@OZ1SEJ 谢谢,很高兴你觉得好,我现在仍然使用那个类 :) - Accountant م

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接