如何使用 PHP 和 PDO 以及准备好的语句创建安全(避免 SQL 注入)分页?

How to create safe (avoid SQL injection) pagination using PHP and PDO and prepared statement?

我正在搜索创建 PDO 分页,我发现 ,它容易受到 SQL 注入。

我想知道将此代码转换为 SQL 注入安全的最简单方法。

更新#1 在我的例子中,我想添加可选的 where 语句并根据用户输入排序

我的代码是:

<?php
$limit = 2;
$order_by   = filter_input(INPUT_GET, 'order_by');
$order_dir  = filter_input(INPUT_GET, 'order_dir');
$query_research_str = filter_input(INPUT_GET, 'search_str');
$query = "SELECT * FROM kategori";
// If search string
if ($query_research_str) {
    //$dbmanager->where('user_name', '%' . $query_research_str . '%', 'like');
    $query = $query . "where user_name like %".$query_research_str."% ";
}
// If order direction option selected
if ($order_dir) {
    //$dbmanager->orderBy($order_by, $order_dir);
    $query = $query . " order By ".$order_by." ".$order_dir;
}

$s = $db->prepare($query);
$s->execute();
$total_results = $s->rowCount();
$total_pages = ceil($total_results/$limit);

if (!isset($_GET['page'])) {
    $page = 1;
} else{
    $page = $_GET['page'];
}



$starting_limit = ($page-1)*$limit;
$show  = $query." LIMIT $starting_limit, $limit";

$r = $db->prepare($show);
$r->execute();

while($res = $r->fetch(PDO::FETCH_ASSOC)):
?>
<h4><?php echo $res['id'];?></h4>
<p><?php echo $res['nama_kat'];?></p>
<hr>
<?php
endwhile;


for ($page=1; $page <= $total_pages ; $page++):?>

<a href='<?php echo "?page=$page"; ?>' class="links"><?php  echo $page; ?>
 </a>

<?php endfor; ?>

如果您能够将 SQL 查询作为存储过程放在 SQL 服务器上,这应该会处理您潜在的 SQL 注入。

转换为整数,如 $page = (integer) $_GET['page'];。 这样,如果其中有任何注入代码,它就会在翻译中丢失。

我有几个意见。

$order_by   = filter_input(INPUT_GET, 'order_by');
$order_dir  = filter_input(INPUT_GET, 'order_dir');
$query_research_str = filter_input(INPUT_GET, 'search_str');

这不会使变量安全。您没有指定任何类型的过滤器作为 filter_input 的第三个参数。文档说:

If omitted, FILTER_DEFAULT will be used, which is equivalent to FILTER_UNSAFE_RAW. This will result in no filtering taking place by default.

换句话说,它与使用原始 GET 变量一样不安全:

$order_by   = $_GET['order_by'];
$order_dir  = $_GET['order_dir'];
$query_research_str = $_GET['search_str'];

您应该对 search_str 等值使用查询参数,但不能对非 SQL 值的内容使用查询参数。就像 ORDER BY 子句中的列名和 SQL 关键字。

那么如何安全使用这些呢? 白名单。

在您的类别 table 中创建一个列数组,这些列是合理的排序选择:

$columns = ['user_name', 'create_date'];

那么如果输入在这个数组里面,就可以用了。否则使用默认值。

$order_by = 'user_name';
if (array_search($_GET['order_by'], $columns)) {
    $order_by = $_GET['order_by'];
}

$order_dir = 'ASC';
if ($_GET['order_dir'] == 'DESC') {
    $order_dir = 'DESC';
}

这样,值只能是您在代码中预先验证过的值。不可能进行 SQL 注入,因为如果输入了一些不同的值,它将不会匹配您的代码允许的任何值,因此输入将被忽略以支持您的默认值。

对于查询研究字符串,您应该将其添加到参数数组中,并在您 execute() 查询时传递它。不要将不安全的变量直接插入到您的 SQL 查询字符串中。

$query = "SELECT * FROM kategori WHERE true";

$params = [];
if ($query_research_str) {
    $query .= " AND user_name LIKE ?";
    $params[] = "%{$query_research_str}%";
}

$query .= " ORDER BY `{$order_by}` {$order_dir}";

$s = $db->prepare($query);
$s->execute($params);

我也会对你低效的分页代码提出意见。您是否知道在调用 rowCount() 之前,您的脚本必须获取 所有 结果?因此,无需 运行 查询 再次 使用 LIMIT 和偏移量。您已经拥有所有数据,所以在您的数据库服务器上放轻松,只需从结果数组中取出一部分即可:

$rows = $s->fetchAll(PDO::FETCH_ASSOC);
$total_rows = $s->rowCount();
$page = (int) $_GET['page'] ?? 1;
$offset = ($page-1) * $limit;
$end = min($total_rows, $offset + $limit);

for ($i = $offset; $i < $end; $i++) {
?>
<h4><?php echo $row[$i]['id'];?></h4>
<p><?php echo $row[$i]['nama_kat'];?></p>
<hr>
<?php
}