【问题标题】:How to check for the sanity of sql query in PHP如何在 PHP 中检查 sql 查询的健全性
【发布时间】:2019-01-27 06:40:52
【问题描述】:

我正在用 PHP 编写一个 DbAdapter。为了避免 sql 注入攻击,对于条件选择,我需要一种方法来检查我要运行的 SQL 查询的健全性。鉴于准备好的语句使实现非常复杂,在类的核心执行之前,有没有一种快速的方法来检查 sql 查询的健全性(特别是WHERE 子句,就像这里的情况一样)?例如,为恶意或可疑查询返回 false 的辅助方法就可以了。

我的班级代码:

require_once './config.php';

class DbAdapter
{

    private $link;


    /**
     * DbAdapter constructor.
     */
    public function __construct()
    {
        $this->link = new mysqli(DBHOST, DBUSER, DBPASS, DBNAME);
        if ($this->link->connect_errno) {
            die($this->link->connect_error);
        }
    }

    /**
     * @param $table
     * @param array $columns
     * @param string $condition
     * @return bool|mysqli_result
     */
    public function select($table, $columns = [], $condition = "")
    {
        $colsString = $this->extractCols($columns);
        $whereString = $this->extractConditions($condition);
        $sql = "SELECT $colsString FROM `$table` " . $whereString;
        return $this->link->query($sql);
    }

    public function __destruct()
    {
       $this->link->close();
    }

    private function extractCols(array $columns)
    {
        if(!$columns) { return '*';}
        else {
            $str = "";
            foreach($columns as $col) {
                $str .= "$col,";
            }
            return trim($str, ',');
        }
    }

    private function extractConditions(string $conditions)
    {
        if(!$conditions) {
            return "";
        }
        else {
            $where = "WHERE ";
            foreach ($conditions as $key => $value){
                $where .= "$key=" . $conditions[$key] . "&";
            }

            return trim($where, "&");
        }
    }
}

【问题讨论】:

    标签: php sql sql-injection


    【解决方案1】:

    简答

    您可以使用EXPLAIN,如EXPLAIN SELECT foo FROM table_bar。然而,如何以编程方式解释结果的“理智”是一个更加困难的问题。您需要对“健全”进行编程定义,例如“检查超过 n 行”或“涉及超过 t 个表”。

    SQL 注入

    您提到您的动机包括想要“避免 sql 注入攻击”。如果这就是您担心的问题,那么这里最重要的是避免将任何用户数据连接到查询中。如果您连接 any 用户数据,SQL injection 是可能的,而且它非常非常难以检测。完全阻止它会更好。

    坦率地说,这段代码让我毛骨悚然:

    $where = "WHERE ";
    foreach ($conditions as $key => $value){
        $where .= "$key=" . $conditions[$key] . "&";
    }
    

    没有办法让它足够安全或足够健全地检查它。你可能会想,“是的,但所有条件都应该只包含数字”,或者类似的容易验证的东西,但你不能安全地依赖它。当您在明年、下周或明天修改代码并添加字符串参数时会发生什么?即时漏洞。

    您需要使用准备好的语句,而不是将变量连接到查询中。仅仅逃避你的变量是不够的。见How can I prevent SQL injection in PHP?

    应用程序设计的一些注意事项

    请注意,这通常是您在将查询部署到生产环境之前执行的操作,而不是即时执行。如果您正在构建允许用户构建自己的查询的收费,则可能无法避免对查询进行一些即时评估。

    但如果您要处理的只是WHERE 子句中的多个条件,那么只要满足以下两点,查询就会很快(并且您不需要使用EXPLAIN):

    1. 您不使用子查询,例如... WHERE id IN (SELECT id from OtherTable WHERE ...) ...,并且
    2. 您有适当的索引。 (不过,在 >99% 的情况下,您可以在开发时预期到这一点。)

    相关的“战争故事”希望能减轻你的一些恐惧

    我曾经写过一个工具,它允许在每个主要表中有几百万行的数据库上构建和运行各种复杂的查询并针对 MySQL 运行。查询大多是简单的WHERE 条件,例如WHERE lastOrder > '2018-01-01',以及一些(大部分是硬编码的)JOIN 和子查询可能性。我只是积极地索引,从不需要EXPLAIN 任何东西;它从未真正遇到任何性能瓶颈。

    【讨论】:

      【解决方案2】:

      允许任意输入成为 SQL 代码的一部分是一种根本性的设计缺陷。没有办法让它“理智”。

      Database Firewall 等一些技术会尝试按照您的要求执行操作,以检测查询何时受到 SQL 注入攻击的影响。问题是,很难区分被泄露的 SQL 查询与仅包含合法动态内容的 SQL 查询。

      结果是注入检测方法不可靠。他们未能检测到所有的注射案例,并且还误认为是合法的注射案例。

      另一种方法是使用 SQL 查询的白名单。也就是说,枚举给定应用程序使用的所有合法 SQL 查询表单,并仅允许运行这些查询。这要求您在部署之前以一种“教学模式”运行应用程序,以识别所有合法的 SQL 查询。然后打开数据库防火墙以阻止在您进行测试运行时不属于已知 SQL 查询的任何内容。

      这也有缺点。它不考虑需要完全动态的 SQL 查询,例如数据透视表查询或构造条件(例如,您的查询根据应用逻辑在 WHERE 子句中获得可变数量的术语)。

      防止 SQL 注入的最佳方法仍然是使用代码审查。确保使用准备好的语句将任何动态值作为查询参数传递。您声称这使代码“非常复杂”,但事实并非如此。

      $sql = "SELECT ...";
      $stmt = $pdo->prepare($sql);
      $stmt->execute($paramValuesArray);
      

      至少我们可以说,编写您展示的所有将术语附加到 SQL 语句的代码并不复杂。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-12
        • 1970-01-01
        • 2012-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多