【问题标题】:Build a dynamic SQL statement via $_POST通过 $_POST 构建动态 SQL 语句
【发布时间】:2016-10-24 12:51:02
【问题描述】:

因此,这更多是为了获得您对最佳方法的意见。

我有一个我认为非常优雅的方法来构建一个带有直接 WHERE 子句的简单动态 SQL 语句。 WHERE 子句可以包含多个字段,但它是有限的,因为它不允许 不同的 运算符(比较或逻辑)。

我可以用这个构建以下内容:

SELECT * from table_name WHERE field_1 = "value_1" AND field_2 = "value_2";
//or I can do
SELECT * from table_name WHERE field_1 = "value_1" OR field_2 = "value_2";
//or I can do
SELECT * from table_name WHERE field_1 <> "value_1" AND field_2 <> "value_2";

我可以构建以下内容:

SELECT * from table_name WHERE field_1 = "value_1" AND field_2 <> "value_2";
//nor can I do
SELECT * from table_name WHERE field_1 = "value_1" AND field_2="value_2" OR field_3 = "value_3

使用数字和日期时,当我想查找值介于两者之间的记录时,这成为一个真正的问题,我需要用两个不同的值将同一个字段传递两次......不是吗?

SELECT * from table_name WHERE price BETWEEN 10 AND 20;
SELECT * from table_name WHERE date BETWEEN "2016-08-01" AND "2016-08-15";

不要忘记带有“IN”或 LIKE 语句的多个条件,这也不会建立,即:

SELECT * from table_name WHERE field_1 IN("value_1","value_2, "value_3");
SELECT * from table_name WHERE field_1 LIKE "val%";

这是我当前的代码:

// db contains my DB connection
$db = new DB();

$where = 'WHERE';
$criteria = array();

foreach ($_GET as $key => $value) {
    $where = $where.' '.$key.'=? AND';
    array_push($criteria,$value);
}

if(count($_GET) > 0){
   // $sql will look like: SELECT * FROM table_name WHERE field_1 = ? AND field_2 = ?
   // $criteria is an array of values to pair with the above prepared statement. 
   // Will look like: $criteria("value_1", "value_2")
   $sql = 'SELECT * FROM mcl_data_gap '.$where;
   $results = $db->query($sql,$criteria);
} else {
    $sql = 'SELECT * FROM mcl_data_gap';
    $results = $db->query($sql);
}
// .... continue on using above SQL statement

在上面的代码中我使用了 get 但我的假设是 post 也可以。

我想出的唯一想法是插入更多键值对,其中包含编码格式所需的运算符,这样我就可以查找这些运算符并基于它们构建语句,但我只是觉得那里是一种更好的方法,我希望你能提供帮助。

我刚刚想到的另一个选择是在将 SQL 传递给服务器并执行它之前构建它。

或者我可以发布包含整个 WHERE 语句的对象吗?

【问题讨论】:

  • 您的代码易受 SQL 注入攻击。 永远不要相信用户输入,即使是变量名也不行。我不得不尝试一次,因为 PHP 会自动用下划线替换空格,但它不会替换制表符,但是,mysql 接受为空格。因此,试图查询yourfile.php?%27;DROP%09TABLE%09SomeImportantTable;%09--=a 的不友好的人将成功积极精简您的数据库。
  • SQL 注入在这里是个问题。用户可以将$key 的值设为任何值,并给您带来很多问题。您仍然可以构建动态查询,但您应该输入所有 SQL 并使用 $_GET 参数来确定最终查询中使用了哪些 SQL。
  • SQL 字符串和参数被传递给一个类,该类使用 PDO 准备语句来查询我的数据库。我的理解是 PDO 准备好的语句用于防止 SQL 注入。

标签: php mysql sql


【解决方案1】:

您正在使用动态值的查询参数(相等比较的右侧)。这很好。

但您不能将参数用于动态列名(比较的左侧)。这就是您的代码容易受到 SQL 注入攻击的原因。准备好的语句对此无济于事。

解决方案是确保来自 $_GET 键的每个列名实际上是表中的列之一。换句话说,这称为将输入列入白名单。

$mcl_data_gap_columns = ["field_1", "field_2", "field_3"];

您只想处理与表中存在的列列表中的列匹配的 $_GET 参数。任何不在此列表中的内容都应忽略。

对于具有多个值的谓词,您可以在 PHP 中通过以“[]”结尾的 GET 参数命名来访问它们。

$terms = [];
$parameters = [];

// only look for $_GET keys that match one of the known columns.
// this automatically ignores all other $_GET keys.
foreach ($mc_data_gap_columns as $col) {

  // get the single value, or the array of multiple values.
  // convert to an array in either case.
  if (isset($_GET[$col])) {
    $values = (array) $_GET[$col];
    $default_op = "=";
  } elseif (isset($_GET[$col."[]"])) {
    $values = $_GET[$col."[]"];
    $default_op = "IN";
  } else {
    continue;
  }

  // if your comparison is anything other than equality,
  // there should be another request parameter noting that.
  if (isset($_GET[$col."_SQLOP"])) {
    $op = $_GET[$col."_SQLOP"];
  } else {
    $op = $default_op;
  }

只处理已知的操作。如果 $op 不是特定支持的操作之一,请忽略它,否则会抛出错误。

  switch ($op) {
  case "=":
  case ">":
  case "<":
  case ">=":
  case "<=":
  case "<>":
    // all these are simple comparisons of one column to one value 
    $terms[] = "$col $op ?";
    $parameters[] = $values[0];
    break;
  case "BETWEEN":
    // comparisons of one column between two values 
    if (count($values) != 2) {
      error_log("$col BETWEEN: wrong number of arguments: " . count($values));
      die("Sorry, there has been an error in your request.");
    }
    $terms[] = "$col BETWEEN ? AND ?";
    $parameters[] = $values[0];
    $parameters[] = $values[1];
    break;
  case "IN":
    // comparisons of one column IN a list of any number of values
    $placeholders = implode(",", array_fill(1, count($values), "?"));
    $terms[] = "$col IN ($placeholders)";
    $parameters = array_merge($parameters, $values);
    break;
  default:
    error_log("Unknown operation for $col: $op");
    die("Sorry, there has been an error in your request.");
  }
}

最后完成后,你会知道 $terms 要么是一个空数组,要么是一个搜索条件数组。

if ($terms) {
  $sql .= " WHERE " . join(" AND ", $terms);
}

$db->query($sql, $parameters);

上面的代码我没有测试过,但应该能说明思路:

  • 切勿在 SQL 查询中逐字使用 $_GET 输入
  • 始终根据安全值的固定列表过滤输入
  • 或者使用switch 来测试一组固定的安全cases

我刚刚想到的另一个选择是在将 SQL 传递给服务器并执行它之前构建它。

不,不,不!这只是一个被黑客入侵的邀请。千万不要这样做!

如果您认为您的 HTML 页面是别人可以向您的服务器提交请求的唯一方式,那您就错了。 任何人都可以形成他们想要的任何 URL,并将其提交到您的站点,即使它包含您不期望的 GET 参数和值。

【讨论】:

  • Bill,您当然是正确的,感谢您指出这一点,我自己应该认识到我的程序中的弱点。我会花点时间适当地吸收您的建议和反馈。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-18
  • 2013-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-12
相关资源
最近更新 更多