如何使用PHP、PDO和prepared语句创建安全(避免SQL注入)分页?

如何使用PHP、PDO和prepared语句创建安全(避免SQL注入)分页?,php,security,pdo,sql-injection,Php,Security,Pdo,Sql Injection,我正在搜索创建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

我正在搜索创建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; ?>

如果您能够将SQL查询作为存储过程放在SQL server上,则应考虑潜在的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\u DEFAULT,这相当于FILTER\u UNSAFE\u RAW。这将导致默认情况下不进行过滤

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

$order_by   = $_GET['order_by'];
$order_dir  = $_GET['order_dir'];
$query_research_str = $_GET['search_str'];
您应该对搜索字符串之类的值使用查询参数,但不能对非SQL值的内容使用查询参数。类似于ORDERBY子句中的列名和SQL关键字

那么如何安全地使用这些设备呢?白名单

在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查询字符串中插入不安全的变量

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

通常认为它更有效,尤其是在大型记录集中,在x和y之间使用id,而不是限制x,y-因此使用where子句时,准备好的语句仍然是安全的。我认为在mysqli中,您可以为limit子句使用占位符,但在PDO中不能使用占位符-因此,也许id betwwen方法有legs?@RamRaider请检查更新的代码。我想现在更复杂了,我需要一个帮助:@RamRaider-是的,你可以使用placeholers来限制PDO。绑定参数时,只需将其显式设置为INT。传递要执行的参数将不起作用,因为它将被视为字符串。我不完全确定使用PDO是否可行-感谢您的澄清,谢谢您的回答,但问题不是关于SQL server的。我也想使用PDOmySQL允许存储过程,这是一个很好的方法,因为正确使用准备好的语句(如OP在问题中所问的语句)也会考虑任何潜在的SQL注入。我使用PDO sqlite,没有机会使用存储过程。我是一个反对者。我的理由是存储过程处理潜在的SQL注入是错误的。任何人都可以像在应用程序代码中一样轻松地在存储过程中编写不安全的动态SQL。其他变量呢$order_by、$order_dir和$query_research_str$order_dir可以作为整数-1/-1向用户请求$order_by也可以作为整数选项从已知列列表中请求。这些是专门处理分页的变量$query_research_str不是分页变量,看起来像是被未公开的函数过滤。我使用htmlPurifier库来处理这些。