【问题标题】:Preventing SQL injection in PHP Code (code included)防止 PHP 代码中的 SQL 注入(包括代码)
【发布时间】:2020-04-16 03:28:26
【问题描述】:

第一次来这里,所以我会尽量做到彻底。

我是一个相当新的程序员,并开始在一家新公司工作,我一直试图防止在旧项目遗留的特定 PHP 代码中进行一些 SQL 注入,但在我重写它。

这个项目在 Laravel 作为后端,AngularJS 作为前端。

我尝试了几种方法来防止 SQL 注入,但都遇到了相同的错误。

错误:

SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'll.created_at BETWEEN '?' AND '?' UNION SELECT 1 as type, ll.id, ll.name, ll.ema' at line 1

这是有问题的原始代码:

   if (!empty($request['inst'])) {
        $institution = $request['inst'];
        $inst = ' ll.inst_id = ' . $request['inst'] . ' AND ';
        $instCount = ' customers.inst_id = :inst AND ';
        $instEvent = ' location_id = :inst AND ';
    } else {
        $inst = ' ';
        $instCount = ' ';
        $instEvent = ' ';
        $institution = "";
    }
    $startTime = $request['start'];
    $endTime = $request['end'];

$sql = " SELECT 0 as type ,ll.id as lead_name, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM customers ll LEFT JOIN leads_statistics ls  ON ll.id = ls.lead_id"
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ll.created_at BETWEEN '" . $request['start'] . "' AND '" . $request['end']
            . "' UNION SELECT 1 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.meeting_set_first BETWEEN '" . $request['start'] . "' AND '" . $request['end']  
            . "' UNION SELECT 2 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.is_active = 1 AND ls.occurred_date BETWEEN '" . $request['start'] . "' AND '" . $request['end']
            . "' UNION SELECT 3 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.is_active = 1 AND ls.not_show_date BETWEEN '" . $request['start'] . "' AND '" . $request['end']
            . "' UNION SELECT 4 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.is_active = 1 AND ls.cancel_meeting_date BETWEEN '" . $request['start'] . "' AND '" . $request['end']
            . "' UNION SELECT 5 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.is_active = 1 AND ls.sale_date BETWEEN '" . $request['start'] . "' AND '" . $request['end']
            . "' UNION SELECT 6 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.is_active = 1 AND ls.cancel_sale_date BETWEEN '" . $request['start'] . "' AND '" . $request['end']
            . "' UNION SELECT 7 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.extended_date BETWEEN '" . $request['start'] . "' AND '" . $request['end']
            . "' UNION SELECT 8 as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id "
            . " LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE $inst ls.is_active = 1 AND ls.meeting_set_date BETWEEN '" . $request['start'] . "' AND '" . $request['end'] . "'";

$leads = DB::select($sql);

以下是我在仔细阅读互联网和 SO 后使用的一些技术。

  1. 占位符(?而不是所有变量,并在最后创建一个包含所有变量 9 次以上的数组)
  2. 命名绑定示例:DB::select($sql, ["inst" => $institution, "start" => $startTime, "end" => $endTime,])
  3. 命名绑定 使用PDO::ATTR_EMULATE_PREPARES => true, 命名绑定,这样我可以多次使用:inst、:start、:end
  4. 带有@符号的SQL变量绑定

技术结果:

  1. 没有保护 - 工作正常,我收到了正确的结果
  2. 占位符 - 我收到上述错误,但 SQL 在占位符中有正确的数据。我可以获取生成的 SQL 并通过 phpmyadmin 服务器 SQL 查询运行它,并按预期接收数据。
  3. 命名绑定 - 根本不起作用。我收到一条错误消息,指出 SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
  4. PDO::ATTR_EMULATE_PREPARES => true, 的命名绑定 - 有效,但是它会影响我从服务器中提取的所有数据,将诸如数字之类的东西转换为字符串或更糟。这会对到处打字造成严重破坏
  5. SQL 变量绑定 - 也不起作用,收到上述错误

我不确定是否可以将这个巨大的 SQL 查询转换为 Laravel Eloquent 或 Query Builder,尽管我已经尝试过了。

有人有什么想法吗?任何帮助将不胜感激,我已经为此苦苦挣扎了几个星期。

【问题讨论】:

  • 尽量只在设置变量的地方使用占位符(如 $request['inst'])
  • 这能回答你的问题吗? How can I prevent SQL injection in PHP?
  • @Chris 不幸的是,我试过占位符和命名绑定没有效果。我收到一条错误消息,指出 SQLSTATE[HY093]: Invalid parameter number: parameter was not defined
  • 为什么这里的每个表都有 8 个引用?似乎查询本身也可以使用一些更改。
  • @Larnu 这是来自别人工作的旧查询。我愿意接受有关如何更改它的建议,因为每个案例类型非常相似,每个案例之间有 1 或 2 个差异。每个案例都在提取客户信息,但每个案例都有不同的过滤器。将所有这些联合拆分成单独的 SQL 不是一个好主意吗?

标签: php mysql laravel pdo


【解决方案1】:
$institution = $request['inst']??'';

$start = $request['start'];
$end = $request['end'];
$joins = [
    1 => ['date_field' => 'meeting_set_first', 'is_active' => false],
    2 => ['date_field' => 'occurred_date', 'is_active' => true],
    3 => ['date_field' => 'not_show_date', 'is_active' => true],
    4 => ['date_field' => 'cancel_meeting_date', 'is_active' => true],
    5 => ['date_field' => 'sale_date', 'is_active' => true],
    6 => ['date_field' => 'cancel_sale_date', 'is_active' => true],
    7 => ['date_field' => 'extended_date', 'is_active' => false],
    8 => ['date_field' => 'meeting_set_date', 'is_active' => true],
];

$sql = 'SELECT 0 as type ,ll.id as lead_name, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM customers ll LEFT JOIN leads_statistics ls  ON ll.id = ls.lead_id LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE '.($institution?" ll.inst_id = :inst0 AND ":'').' ll.created_at BETWEEN :start0 AND :end0 UNION ';
if ($institution) {
    $placeHolders[':inst0'] = $institution;
}
$placeHolders[':start0'] = $start;
$placeHolders[':end0'] = $end;

$queryJoin = [];
foreach($joins as $key => $join) {
    $queryJoin[] = 'SELECT '.$key.' as type, ll.id, ll.name, ll.email, ll.updated_at, u.employee_name, ls.meeting_set_date, ls.meeting_set_first FROM leads_statistics ls LEFT JOIN customers ll ON ls.lead_id = ll.id LEFT JOIN users u ON ll.userUpdate = u.employee_id  WHERE '.($institution?" ll.inst_id = :inst$key AND ":'').($join['is_active']?' ls.is_active = 1 AND ':'').' ls.'.$join['date_field'] ." BETWEEN :start$key AND :end$key";
    if ($institution) {
        $placeHolders[':inst'.$key] = $institution;
    }
    $placeHolders[':start'.$key] = $start;
    $placeHolders[':end'.$key] = $end;
}

$sql .= implode(' UNION ', $queryJoin);

$leads = DB::select($sql, $placeHolders);

【讨论】:

  • 嘿,我已经尝试过这个解决方案(正如我在 OP 中提到的那样),但收到一条错误消息,指出“SQLSTATE[HY093]: Invalid parameter number: parameter was not defined”。我的印象是,您不能多次重复使用占位符,并且如果您这样做,则需要再次定义它们 ex:DB::select($sql, [':inst' => $institution, ':start' => $request ['start'], ':end' => $request['end'],':inst2' => $institution,]);等等
  • @AriCashriel 你说得对,修复这个查询真是一团糟
  • 谢谢!您编辑的答案有效。将其标记为答案。这个逻辑也将帮助我解决另一个非常相似的 SQL 问题。非常感谢!
猜你喜欢
  • 2010-12-20
  • 2013-02-08
  • 2011-10-18
  • 2022-08-05
  • 2023-03-14
  • 2012-10-21
  • 2017-12-05
  • 2016-12-20
  • 2011-07-05
相关资源
最近更新 更多