通过$_POST构建一个动态SQL语句


Build a dynamic SQL statement via $_POST

所以这更多的是关于获得您对最佳方法的意见。

我有一个我认为是相当优雅的方式来构建一个简单的动态SQL语句与一个直接的WHERE子句。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传递给服务器之前构建SQL并执行它。

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

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

但是不能为动态列名(比较的左侧)使用参数。这就是代码容易受到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对一组固定的安全case s进行测试

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

不,不,不!这只会让你被黑。千万不要这样做!

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