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

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

提问人:Shady Mohamed Sherif 提问时间:12/18/2019 最后编辑:Shady Mohamed Sherif 更新时间:12/21/2019 访问量:583

问:

我正在寻找创建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; ?>
php 安全 pdo sql 注入

评论

0赞 Professor Abronsius 12/18/2019
它通常被认为更有效,尤其是在使用大型记录集而不是 - 时,当您使用 where 子句时,准备好的语句仍然是安全的。我认为在 mysqli 中,您可以为限制子句使用占位符,但在 PDO 中则不能 - 所以,也许 id betwwen 方法有腿?id between x and ylimit x,y
0赞 Shady Mohamed Sherif 12/18/2019
@RamRaider请检查更新后的代码。我认为现在情况更加复杂,我需要帮助:)
0赞 M. Eriksson 12/18/2019
@RamRaider - 是的,您可以将占位孔器用于 PDO。您只需在绑定参数时将参数显式设置为 INT。将参数传递给 将不起作用,因为它将被视为字符串。LIMITexecute()
0赞 Professor Abronsius 12/18/2019
我不完全确定 PDO 是否可行 - 感谢您的澄清

答:

-3赞 in need of help 12/18/2019 #1

如果能够将 SQL 查询作为存储过程放在 SQL Server 上,这应该可以处理潜在的 SQL 注入。

评论

0赞 Shady Mohamed Sherif 12/18/2019
感谢您的回答,但问题与 SQL Server 无关。我想使用 PDO
0赞 Professor Abronsius 12/18/2019
mySQL也允许存储过程,这是IMO的好方法
0赞 M. Eriksson 12/18/2019
正确使用准备好的语句(就像 OP 在问题中询问的那样)也可以处理任何潜在的 SQL 注入。
0赞 Shady Mohamed Sherif 12/18/2019
我正在使用PDO sqlite,没有机会使用存储过程
1赞 Bill Karwin 12/19/2019
我是反对者之一。我的理由是,存储过程“处理潜在的SQL注入”是错误的。任何人都可以在存储过程中编写不安全的动态 SQL,就像在应用程序代码中一样容易。
-1赞 Peter Brand 12/18/2019 #2

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

评论

1赞 Shady Mohamed Sherif 12/18/2019
其他变量呢? 和$order_by$order_dir$query_research_str
0赞 Peter Brand 12/18/2019
$order_dir可以作为整数 - 1 / -1 向用户请求。 也可以作为已知列列表中的整数选项请求。这些是专门处理所询问的分页的变量。 不是分页变量,看起来它正在被一个未公开的函数过滤。我使用 htmlPurifier 库来处理这些。$order_by$query_research_str
2赞 Bill Karwin 12/21/2019 #3

我有几点意见。

$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 等值使用查询参数,但不能对非 SQL 值使用查询参数。类似于 ORDER BY 子句中的列名和 SQL 关键字。search_str

那么如何安全地使用它们呢?白名单。

在 kategori 表中创建一个列数组,这些列是排序的合法选择:

$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 注入,因为如果输入了一些不同的值,它将与代码允许的任何值都不匹配,因此将忽略输入以支持默认值。

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

$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);

我也会对你低效的分页代码发表评论。您是否知道,在调用之前,您的脚本必须获取所有结果?因此,无需使用 LIMIT 和 offset 再次运行查询。您已经拥有了所有数据,因此请轻松使用数据库服务器,只需从结果数组中抽取一个切片:rowCount()

$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
}