【问题标题】:What's a better way to make this insert more secure and safe from injection and manipulation有什么更好的方法可以使这种插入物更安全,避免注射和操作
【发布时间】:2020-11-01 19:02:25
【问题描述】:

我一直在尝试以更安全的方式将函数组合在一起,通过调用要更新的不同列来防止注入或操纵插入。在您看来,此功能是否安全?如果不是,您会建议更好的方法,以及为什么。

当用户更新他们的个人资料或个人资料的特定部分时调用此函数,如您所见,我已经创建了一个数组,其中包含他们可以在该表中更新的所有项目。此外,我得到的 user_id 来自附加到他们会话的安全加密 JSON 令牌,他们没有发送。感谢您的宝贵时间。

function updateProfile( $vars, $user_id ) {
    $db = new Database();
    $update_string = '';
    $varsCount = count($vars);
    $end = ',';
    $start = 1;
    $safeArray = array( "gradYear", "emailAddress", "token", "iosToken", "country", 
"birthYear", "userDescription" );
    foreach($vars as $key => $value) {
        if(in_array( $key, $safeArray )) {
            if($start == $varsCount) {
                $end = '';
            }
         
            $update_string .= $key . '=' . '"' . $value . '"' . $end;
        }
            $start++;
    }

    if($start > 0) {
        $statement = "update users set " . $update_string . " where userId = '$user_id'";
        $query = $db->updateQuery( $statement );
        if($query) {
            $response  = array( "response" => 200 );
        } else {
            $response  = array( "response" => 500, "title" => "An unknown error occured, 
please try again");
        }

    }

【问题讨论】:

  • 不,肯定不会。
  • 为什么不完全正确?准备好的语句和参数是确保防止注入攻击的唯一方法。这是标准方法,你会从每个人那里得到相同的答案。所以如果你觉得你的情况有一些额外的维度,那么请说清楚。目前,您正在将用户输入连接到查询中,这是不安全的。限制可以更新的列是一种验证措施,但它无法防止注入攻击
  • 您拥有的唯一安全措施是验证有效字段。没有任何输入清理,使您受到 SQL 注入的影响。考虑使用准备好的语句,而不是根据用户提供的数据制作查询。
  • @Markus AO 你能告诉我 1 个例子,说明如何对该语句执行任何恶意操作,以便我更好地理解。因为我显然很难理解这一点。请和谢谢你
  • ????:“看起来您正在编写自己的 ORM。您是否考虑过使用已经编写、测试和广泛支持的 ORM,例如 RedBeanPHPDoctrine 或 @ 987654323@?”

标签: php mysql sql-injection


【解决方案1】:

正如上面的 cmets 所建议的,使用查询参数来保护自己免受 SQL 注入是值得的。

您要求举例说明如何进行恶意操作。事实上,它甚至不需要是恶意的。任何合法包含撇号的无辜字符串都可能破坏您的 SQL 查询。恶意 SQL 注入利用了这个弱点。

通过将动态值与 SQL 查询分开,直到查询被解析后,这个弱点得到了修复。我们在 SQL 字符串中使用查询参数占位符,然后使用prepare() 对其进行解析,然后将execute() 准备好的查询时的值组合起来。这样它就很安全了。

以下是我编写函数的方式。我假设使用支持命名查询参数的PDO。我建议使用 PDO 而不是 Mysqli。

function updateProfile( $vars, $userId ) {
    $db = new Database();
    $safeArray = [
        "gradYear",
        "emailAddress",
        "token",
        "iosToken",
        "country",
        "birthYear",
        "userDescription",
    ];
    // Filter $vars to include only keys that exist in $safeArray.
    $data = array_intersect_keys($vars, array_flip($safeArray));

    // This might result in an empty array if none of the $vars keys were valid.
    if (count($data) == 0) {
        trigger_error("Error: no valid columns named in: ".print_r($vars, true));
        $response = ["response" => 400, "title" => "no valid fields found"];
        return $response;
    }
    
    // Build list of update assignments for SET clause using query parameters.
    // Remember to use back-ticks around column names, in case one conflicts with an SQL reserved keyword.
    $updateAssignments = array_map(function($column) { return "`$column` = :$column"; }, array_keys($data));
    $updateString = implode(",", $updateAssignments);

    // Add parameter for WHERE clause to $data. 
    // This must be added after $data is used to build the update assignments.
    $data["userIdWhere"] = $userId;
    
    $sqlStatement = "update users set $updateString where userId = :userIdWhere";

    $stmt = $db->prepare($sqlStatement);
    if ($stmt === false) {
        $err = $db->errorInfo();
        trigger_error("Error: {$err[2]} preparing SQL query: $sqlStatement");
        $response = ["response" => 500, "title" => "database error, please report it to the site administrator"];
        return $response;
    }
    
    $ok = $stmt->execute($data);
    if ($ok === false) {
        $err = $stmt->errorInfo();
        trigger_error("Error: {$err[2]} executing SQL query: $sqlStatement");
        $response = ["response" => 500, "title" => "database error, please report it to the site administrator"];
        return $response;
    }

    $response = ["response" => 200, "title" => "update successful"];
    return $response;
}

【讨论】:

  • 注意我写了这段代码,但我没有测试它。如果有任何小错误,例如缺少分号,我会留给您解决。
【解决方案2】:

除了出色的比尔回答之外,还有一个小建议:总是让你的方法一次只做一件事。如果一个方法的工作是更新一个数据库,那么它应该只更新一个数据库而不是别的,包括 HTTP 交互。想象一下,这种方法可以在非 AJAX 上下文中使用,也可以在没有 Web 服务器的情况下使用,但可以通过命令行实用程序使用。那些 HTTP 代码和 JSON 响应看起来完全偏离了轨道。所以有两个类:一个用于更新数据库,一个用于与客户端交互。它将使您的代码更加简洁和可重用。

此外,切勿为每个查询创建与数据库的新连接。相反,有一个现成的连接并将其用于所有数据库交互。

function updateProfile($db, $vars, $userId )
{
    $safeArray = array( "gradYear", "emailAddress", "token", "iosToken", "country", 
"birthYear", "userDescription" );

    // let's check if all columns are safe
    if (array_diff(array_keys($vars), $safeArray)) {
        throw new InvalidArgumentException("Unknown columns provided");
    }
        
    $updateAssignments = array_map(function($column) { 
        return "`$column` = :$column"; }, array_keys($vars)
    );
    $updateString = implode(",", $updateAssignments);

    $vars["userIdWhere"] = $userId;
    
    $sqlStatement = "update users set $updateString where userId = :userIdWhere";    
    $db->prepare($sqlStatement)->execute($vars);
}

你看,它让你的代码简洁易读。而且,最重要的是 - 可重复使用。你不必让你的方法臃肿。如果使用得当,PHP 是一种非常简洁的语言

【讨论】:

  • 非常感谢,现在一切都变得有意义了。这是今晚学习的一大进步,我真的很感激!
  • 顺便说一句,代码使用的是普通的 PDO,而你的 $db 是别的东西。考虑重写 updateQuery() 方法,使其接受查询和带参数的数组并使用准备好的语句执行它
  • 赞成,我认为让函数按照你说的做一件事是个好建议。但是,如果您的查询参数是命名参数样式,我认为您不应该在显示的方式中使用array_values()
  • @BillKarwin 谢谢,已修复,我想让它定位,以避免可能与占位符名称中不允许的字符发生冲突,但中途改变了主意
猜你喜欢
  • 2020-05-05
  • 1970-01-01
  • 2012-07-25
  • 1970-01-01
  • 1970-01-01
  • 2014-05-24
  • 2017-06-30
  • 2014-11-21
  • 1970-01-01
相关资源
最近更新 更多