开关函数SQL注入安全吗?

Is the switch-function SQL-injection safe?

提问人:suverenia 提问时间:2/15/2021 最后编辑:Dharmansuverenia 更新时间:2/16/2021 访问量:176

问:

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

我有这个表格,其中包含可以属于以下三个类别(汽车、自行车、卡车)之一的车辆:

车辆 汽车 自行车 卡车
汽车 1 x
汽车 2 x
自行车 1 x
卡车 1 x

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

用户输入:

 URL: ?category=cars

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

类车辆的构造者:

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 注入?

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

php mysql pdo sql 注入

评论

0赞 suverenia 2/15/2021
您将如何使用准备好的语句来获取列名?我对此有疑问,这就是我求助于切换的原因。
1赞 sticky bit 2/15/2021
看起来总体上还可以。但是数据库设计还不错,因为“关于数据库列名称的提示”。但是,除非车辆可以有多种类型,否则表示类型的列可能是更好的选择。如果车辆可以有多种类型,则车辆和类型之间的桥接表可能会更好。
0赞 suverenia 2/15/2021
@stickybit 如果我不使用 break,它是空的吗?它不就变成了“汽车”一样吗?表示类型的列是有问题的,因为此处显示的表非常简化。有 50 个类别,每辆车可以属于其中的几个。
0赞 sticky bit 2/15/2021
关于默认的事情是我的错。我已经编辑了那个错误的部分。
4赞 sticky bit 2/15/2021
所以你有 50 列?o_O这就是为什么这是一个糟糕的设计。每当您需要一个新类别时,您都需要一个新列。关系表不是电子表格。如果您在添加新数据时需要更改列,这是一个强有力的指标,表明该架构存在根本缺陷......有一个单独的类型表和一个将车辆分配给类型的桥接表。

答:

4赞 user15209082 2/15/2021 #1

简短的回答: 是的

长答案:

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

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

许多人告诉你在每个请求中都使用准备好的语句,但只有当你在查询中直接使用用户输入(如用户名、命令、电子邮件)时,你才必须使用它们。

评论

0赞 ADyson 2/15/2021
另外,值得指出的是,在这种情况下,参数和预准备语句在任何情况下都不能使用,因为 OP 注入的是列名,而不是值。
-1赞 advancedBoolean 2/15/2021 #2

当您将 Coulmn 作为参数时,您运行的设计很糟糕。

但是,代码原样是安全的,因为它是固定的。

请至少做。 取代: $sql = “从车辆中选择 * $this->类别不为 NULL”;

$param = $usetheconnection->real_escape_string($this->类别); $sql = “从'{$param}'不为空的车辆中选择*”;

然后重做设计

3赞 Steven 2/15/2021 #3

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

为什么安全

在这种情况下,使您免于SQL注入的一件事是您在语句中设置了一个大小写。如果没有这个词,你就会对SQL注入持开放态度。让我们来玩一玩:defaultswitch

有效输入示例

  1. 用户输入bikes
  2. 代码将属性设置为categorybikes
  3. 您的运行并被找到,因此它返回为switchbikesbikecategory

无效的输入示例

  1. 用户输入hairStraightener
  2. 代码将属性设置为categoryhairStraightener
  3. 您的运行未找到,因此它返回为 ;情况就是这样switchhairStraightenercarcategorydefault

无效大小写,无默认值

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

现在,假设一个用户输入了类似的东西:

1; DROP TABLE vehicles; --

// OR...

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

现在您丢失了大量数据,或者商店中的所有东西都要花费 1 英镑(讨价还价!

改进事物

你走对了:如果你需要直接在SQL查询中输入一个变量,你需要将可接受的项目列入白名单,并只使用这些项目。有不同的方法可以做到这一点......

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

开关

我看到你这样做的最大问题是,如果有人来查看你的代码,他们很可能会看到你已经有效地设置了默认值,因此删除了大小写;这将使您对 SQL 注入持开放态度。switchdefault

因此,您应该相应地更新代码:

  • 切勿将用户输入设置为属性category
  • 设置 on 声明的默认属性category

F.D.(英语:F.D.)

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+,你可以使用。在本例中,您可以将其视为类型敏感语句:matchswitch

注意:如果您不提供默认大小写,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

长期解决问题

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

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 = ?

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

注意

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

  1. 创建具有适当数据类型、属性等的表。
  2. DESCRIBE表格并提取不同的类型(、等)vehiclescarbike
  3. 将类型插入到表中type
    • 例如,这一切都可以通过几行PHP自动完成
  4. 编写一个简短的脚本,根据列遍历车辆表的每一行和记录insertvechicleTypenot null
  5. 检查您的数据(根据需要进行备份)
  6. 从中删除不再需要的列vehicles