switch-function SQL-injection 安全吗?

Is the switch-function SQL-injection safe?

我正在尝试访问具有可变列名称的数据库中的条目。

我有这个 table 包含属于以下三个类别(汽车、自行车、卡车)之一的车辆:

vehicle car bike truck
Car 1 x
Car 2 x
Bike 1 x
Truck 1 x

使用 OOP 和 PDO,我正在尝试访问属于某个类别的车辆。像这样:

用户输入:

 URL: ?category=cars

以下所有内容都在名为“Vehicles”的 class 中。

class 车辆的制造商:

public function __construct() {

  $this->category = $_GET["category"] ?? "cars";

  switch ($this->category) {
  default: //Avoiding db-error messages by setting default category to "car"
  case "cars":
    $this->category = "car";
    break;
  case "bikes":
    $this->category = "bike";
    break;
  case "trucks":
    $this->category = "truck";
    break;
 }

然后我从数据库中访问与类别相对应的条目:

public function getVehiclesFromCategory() {

    $sql = "SELECT * FROM vehicles WHERE $this->category IS NOT NULL";
    $stmt = $this->connect()->query($sql);

    while ($row = $stmt->fetch()) {
        $row["vehicle"]."<br>";
    }
}

然后我创建对象以从所选类别中获取输出:

$Vehicles = new Vehicle();
$Vehicles->getVehiclesFromCategory();

我基本上是将用户输入与预定义值相关联。这足以避免 SQL-注入吗?

我确实意识到我使用了一个糟糕的数据库设计,因为用户不应该得到任何关于数据库列名称的提示。我也知道我应该避免可能对黑客有用的与数据库相关的错误消息(这就是我使用默认开关的原因)——但我现在需要使用当前的数据库模型进行快速修复。

简答: 是

长答案:

是的,你是, 因为您实际上没有将用户输入用作数据库字段,所以他们无法对其进行操作。

只要您不直接将用户输入输入您的数据库,您就不会遇到 mysql 注入问题。

许多人告诉您在每次请求时都使用准备好的语句,但只有在您直接在查询中使用 userinput 时才需要使用它们,例如用户名订购电子邮件。

当你将列作为参数时,你是运行一个糟糕的设计。

然而,代码是安全的,因为它是固定的。

请至少做。 代替 : $sql = "SELECT * 来自 $this->category 不为空的车辆";

$param = $usetheconnection->real_escape_string($this->category); $sql = "SELECT * 来自 `{$param}` 不为空的车辆";

然后重做设计

看起来这个问题(“安全吗?”)已经得到解答。然而,如何以及为什么似乎有点悬而未决,所以这里有一些进一步的信息......

为什么它安全

在这种情况下,ONE 使您免于 SQL 注入的是您在 [=18] 中设置了一个 default 案例=] 语句。如果没有这个词,您将对 SQL 注入持开放态度。让我们一起玩吧:

有效输入示例

  1. 用户输入bikes
  2. 您的代码将 category 属性 设置为 bikes
  3. 你的 switch 运行并找到 bikes 所以它 returns bike 作为 category

输入示例无效

  1. 用户输入hairStraightener
  2. 您的代码将 category 属性 设置为 hairStraightener
  3. 您的 switch 运行但未找到 hairStraightener,因此它 returns car 作为 category;这是 default 案例

没有默认值的无效大小写

  1. 用户输入hairStraightener
  2. 您的代码将 category 属性 设置为 hairStraightener
  3. 您的 switch 运行但未找到 hairStraightener,因此 category 未更新并保持为 hairStraightener

现在,想象一下用户输入了如下内容:

1; DROP TABLE vehicles; --

// OR...

1; UPDATE TABLE vehicles SET price = 1; --

现在您丢失了大量数据,或者您商店中的所有商品都需要 1 英镑(便宜货!)

改善事物

您是对的:如果您需要将变量直接输入到 SQL 查询中,您需要将 acceptable 项目列入白名单并仅使用这些项目。有不同的方法...

  1. 就像你有一个 switch/case
  2. match (PHP 8+)
  3. 使用 array 和查找
  4. 根据数据库模式检查变量

切换

我看到的最大问题是,如果有人过来查看您的代码,他们很可能会发现您有效地设置了默认值switch 之前删除 default 案例;这会让你对 SQL 注入持开放态度。

所以你应该相应地更新你的代码:

  • 永远不要将用户输入设置为 category 属性
  • 在声明时设置 category 的默认值 属性

例如

public $category = "car";

public function __construct()
{
  switch ($_GET["category"] ?? null) {
    case "cars":
        $this->category = "car";
        break;
    case "bikes":
        $this->category = "bike";
        break;
    case "trucks":
        $this->category = "truck";
        break;
    }
}

匹配

@Dharman 所述,如果您的服务器是 运行 PHP 8+,则可以使用 match。在这种情况下,您可以将其视为类型敏感的 switch 语句:

注意:如果不提供 default 大小写,match 将抛出错误;或者更确切地说,如果提供的值无法匹配!

function __construct()
{
    $this->category = match($_GET["category"] ?? "cars") {
        "cars"   => "car",
        "bikes"  => "bike",
        "trucks" => "truck",
        default  => "car"
    };
}

数组查找

private $allowedFields = [
    "cars"  => "car",
    "bikes" => "bike",
];

public function __construct()
{
  $this->category = $this->allowedFields[$_GET["category"] ?? "cars"];
}

数据库模式

最后,您可以通过检查数据库架构(类似于 DESCRIBE vehicles)并检查输入是否与其中一个列名匹配来自动生成 safe 字段。在您的情况下,尽管这可能不是最好的主意,因为您仍然可以让某人输入非预期的 真实字段 。也许这不会是灾难性的,但绝对不是故意的!

长期解决问题

正如其他人所说,这是一个有很大缺陷的数据库设计。大概看起来像:

vehicles
    id
    make
    model
    price
    ...
    bike
    car
    van
    truck
    ...

什么时候应该更像:

vehicle     <    vehicleType      >    type
    id              id                    id
    make            vehicle_id            name
    model           type_id
    price
    ...

然后您将 SQL 更新为如下所示:

SELECT
    vehicle.id, vehicle.make, vehicle.model, vehicle.price,
    type.name
FROM vehicle
JOIN vehicleType on vehicle.id          = vehicleType.vehicle_id
JOIN type        on vehcileType.type_id = type.id
WHERE type.name = ?

现在您可以使用准备好的语句来确保完全安全

N.B.

创建两个 table 并将现有的更新到其中似乎是一个耗时的过程。但实际上不会花那么长时间。过程:

  1. 使用适当的数据类型、属性等创建 table
  2. DESCRIBE vehicles table 并提取不同的类型(carbike 等)
  3. 将类型插入 table type
    • 这都可以通过几行PHP自动完成,例如
  4. 编写一个简短的脚本,根据 not null 的列,将车辆的每一行 table 和 insert 记录循环到 vechicleType table ]
  5. 检查您的数据(按要求备份)
  6. vehicles
  7. 中删除不再需要的列