PHP MYSQLI 预准备语句 - 是否可以使用占位符来动态生成 WHERE 子句的元素?[复制]

PHP MYSQLI Prepared Statements - Is it possible to use placeholders to dynamically generate elements of a WHERE clause? [duplicate]

提问人:kdft02 提问时间:7/13/2023 最后编辑:kdft02 更新时间:7/13/2023 访问量:70

问:

我最近一直在努力修复 PHP 遗留应用程序的安全漏洞。我们重构了所有 MYSQLI 查询,以使用预准备语句。这基本上是直截了当的,但我遇到了一些孤立的情况,在这些情况下,我需要对动态生成的 WHERE 子句使用占位符 (?),并且可以根据用户输入具有不同的元素。我无法找到显示如何实现此目的的文档,因此我可能需要更改我的方法。

在用户选择输入之前,我不知道需要将哪些项目添加到 where 子句中。解决这个问题的最佳方法是什么?

原始的、正常运行的查询的工作方式如下:

$query ="SELECT [fields] FROM table1, table2 where (table1.id = table2.cc_id) $where order by num, name, due_date";

$where是一系列动态的 AND 语句。我只知道页面运行时 AND 子句/值的数量。例:

AND table1.business_line_id IN (2) AND table1.client_id IN (1,2,3,5) AND table1.application_id in (7,47) AND table1.region_id in (1,4,5,6,8,14,15,21)

与此项相关的其余代码:

$params = array($where);

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
if(!$stmt = $connection->prepare($query)) {
    showerror($connection);
    die();
}
// $stmt -> bind_param(get_types($params),...$params);
$stmt -> bind_param('s', $params);
$stmt -> execute();

参数数组返回一个长度为 1 的数组,其正确的$where值为 string。我试图实现一个占位符,但这传统上不遵循我们使用 field = placeholder (user_id = ?) 或 VALUES(?,?,?) 的占位符结构。是否可以生成一个查询,在其中我们使用具有此语法的 WHERE/AND 子句的占位符动态生成的部分?

下面是一个不起作用的示例。

    // $query ="SELECT [fields] from table1, table2 WHERE (table1.id = table2.cc_id) ? order by num, name, due_date";

可以理解的是,MYSQLI 的 $stmt = $connection->prepare($query) 在占位符值 (?) 处对此有疑问。如果我尝试直接包含$where变量,则 bind_param() 调用会出现问题,因为它没有要绑定的参数(变量数量不匹配)。

这是一个确实有效的示例,但我担心它无法正确防止 SQL 注入。只需传递$where并删除任何参数绑定即可获得正确的输出。

$query ="SELECT [fields] FROM table1, table2 where (table1.id = table2.cc_id) $where order by num, name, due_date";
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
if(!$stmt = $connection->prepare($query)) {
        // added this during debug to show the malformed query
    showerror($connection);
    die();
}

$stmt -> execute();
if (!$result = $stmt->get_result()) showerror($connection);

有没有办法使用占位符传递此子句?

编辑:为清楚起见$where添加构建逻辑。

$where = "and cc.business_line_id in ($business_line_ids)";
// some code
foreach($ar_select as $value) {
    if (isset($_POST["${value}_list"])) {
        $thisfield = ($value == "assignee_status_id") ? "table2.status_id" : "cc.$value";
        $this_id_list = implode(",", $_POST["${value}_list"]);
        $where .= " AND $thisfield in ($this_id_list) ";
    }
}
// some code
$ar_dates = array("status_date","target_date","due_date");
foreach($ar_dates as $thisdate) {
    if (strlen($_POST["$thisdate"])) {
        $date = $_POST["$thisdate"];
        $op_date = $_POST["op_$thisdate"];
        $where .= " AND $thisdate " . date_operator($op_date) ." '$date' ";
    }
}

if (strlen($_POST["keyword_name"])) {
    $keyword_name = $_POST["keyword_name"];
    $where .= " AND asn.name LIKE '%$keyword_name%' ";
}
PHP MySQLI

评论

1赞 David 7/13/2023
构造值的方式与构造任何字符串没有什么不同。您可以连接字符串、在字符串中插值变量等。只要字符串不是直接从用户控制的值生成的,而只是控制的值,就没有区别。SQL 注入仅在执行该 SQL 代码时发生。只要生成的查询使用值占位符,并且执行该查询使用参数,就与静态创建该字符串没有什么不同。$query
0赞 ADyson 7/13/2023
关键在于字符串是如何构造的,我们看不到。这需要逐步建立,大概是基于一些用户输入值?它需要确保在正确的位置使用 ?s,并且对于每个 ?,将一个值添加到单独的值数组中,然后在查询运行时将该值添加到参数列表中。$where
0赞 kdft02 7/13/2023
@ADyson这是有道理的,我在页面上添加了构建$where的所有元素,其中一部分来自 _POST 美元。听起来我需要做的是将这些值分离到一个具有多个索引的数组中,这样我就可以在每个值上显式使用绑定参数。此外,我发现这篇文章(stackoverflow.com/questions/2138825/...)指出,除了带有占位符的值之外,我无法传递任何东西。看来我需要重构$where的构建方式才能使其正常工作并考虑 SQL 注入。
2赞 Barmar 7/13/2023
使用 PDO 比使用 mysqli 更容易做到这一点,因为您可以将参数值放在数组中并将其传递给 。mysqli 在 PHP 8.0 中添加了该功能,但如果您使用的是旧版本,则更难。$stmt->execute()
1赞 ADyson 7/13/2023
It sounds like what I need to do is separate these values out into an array with multiple indices so I can explicitly use bind params on each value.是的,基本上。例如,你有这样,你可以添加一个单独的?将其中的每个项目放入字符串中,并将值放在单独的列表中进行绑定。正如 Barmar 所说,使用 PHP 8 或 PDO 要容易得多$_POST["${value}_list"]

答:

0赞 Sammitch 7/13/2023 #1

您只能参数化,而不能参数化语句。

基本上,SQL 解释器编译 SQL 语句,然后插入参数值。此时,您无法重新调用编译来找出参数内部的内容,这将使之前的编译无效并使参数化变得毫无意义。我们想编译语句一次从不插值参数。

也就是说,您可以修改构造语句的方式以包含参数堆栈,然后随时推动内容。

下面是一个简化/人为的例子:

$params = [];
$clauses = [];

$input = [
    ['foo = ?', ['fooval']],
    ['bar BETWEEN ? AND ?', ['start', 'end']],
    ['baz = 42', []],
    ['bof IS NOT ?', ['notbof']]
];

foreach($input as $item) {
    $clauses[] = $item[0];
    $params = array_merge($params, $item[1]);
}

$where = sprintf('WHERE %s', implode(' AND ', $clauses));

var_dump($where, $params);

输出:

string(67) "WHERE foo = ? AND bar BETWEEN ? AND ? AND baz = 42 AND bof IS NOT ?"
array(4) {
  [0]=>
  string(6) "fooval"
  [1]=>
  string(5) "start"
  [2]=>
  string(3) "end"
  [3]=>
  string(6) "notbof"
}

评论

0赞 Your Common Sense 7/13/2023
此代码在 $input 方面有一个弱点。一个非常薄弱的点。我想说,这违背了整个事件的真正目的。
0赞 Your Common Sense 7/13/2023
为什么要写“简化”答案?在“简化”形式中,OP个人已经在评论中得到了答案。虽然一个功能齐全的答案永远不应该被“简化”,因为每个答案都有双重目的:帮助 OP 和帮助后来的每个人。为了这些人,答案必须是坚实而复杂的,而不是“简化的”