php - 如何使用 PHP 和 PDO 和准备好的语句创建安全(避免 SQL 注入)分页?
问题描述
我正在搜索创建 PDO 分页,我发现了这个答案,它容易受到 SQL 注入的影响。
我想知道将此代码转换为 SQL 注入安全的最简单方法。
更新 #1 在我的情况下,我想添加可选的 where 语句和 order by 基于用户输入
我的代码是:
<?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; ?>
解决方案
我有几条评论。
$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 的第三个参数。文档说:
如果省略,将使用 FILTER_DEFAULT,相当于 FILTER_UNSAFE_RAW。这将导致默认情况下不进行过滤。
换句话说,它与使用原始 GET 变量一样不安全:
$order_by = $_GET['order_by'];
$order_dir = $_GET['order_dir'];
$query_research_str = $_GET['search_str'];
您应该将查询参数用于类似 your 的值search_str
,但不能将查询参数用于非 SQL 值的内容。类似于 ORDER BY 子句中的列名和 SQL 关键字。
那么如何安全地使用这些呢?白名单。
在您的 categori 表中创建一个列数组,这些列是排序的合法选择:
$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 和 offset再次运行查询。你已经拥有了所有的数据,所以在你的数据库服务器上放轻松,从结果数组中取出一个片段:
$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
}
推荐阅读
- javascript - v-for 到 v-if 的 VueJS 过滤器
- python - Discord.py 通过用户 ID 禁止特定的人
- html - 防止html元素之间的换行
- mysql - 选择是否存在于另一个表中,带有 AND 条件
- eslint - 如何强制 Prettier 使用总是引用道具(并尊重我的 eslint 规则)?
- qt - 通过 GUI 线程进行的更改不会反映在 workerscript 中
- c++ - abour cerr 和 clog 的 C++ 程序在 Clion 和终端中的运行方式不同
- android - 更改 NumberPicker 分隔线颜色不适用于 Android Api 29+
- c# - 在记录中定义一个属性两次
- asp.net-core - 在 Azure 应用服务 Web 应用中存储 appsettings.json